Repository: Cosmian/cloudproof_rust (language: python)

Issue:
Modify return of `ffi_unwrap!()`
# Description

Unwrapping using `ffi_unwrap!()` [returns `1_i32`](https://github.com/Cosmian/cloudproof_rust/blob/0aa373b0abacaba020bf4a028028069d7bc9012d/crates/ffi_utils/src/macros.rs#L36). However, this is a reserved value in some FFI interfaces (e.g. in Findex FFI interface, an error code of `1` means that the output buffer was not allocated enough memory). This leads the caller to perform a second call to the FFI function before throwing an exception (since the caller's code should support only one failure of this type).

If the error happened *after* the Findex function was performed, this would lead to duplicate all operations performed on the index. This would lead to duplicate network  communication, insert garbage for `add`s, or - more dangerously - duplicated deletions for `delete`s. 

# Solution

A different error code could be used that would not conflict with any meaningful error code (e.g. `-1`). However this would mean that unwrapped errors cannot be categorized using the error code (a serialization error would return the same code as a conversion error). It may be preferable to accept a third argument:
```rust

/// Unwraps an `std::result::Result`.
///
/// If the result is an error, sets the last error to this error and returns
/// early with 1.
///
/// - `res` : result to unwrap
/// - `msg` : (optional) additional message to use as error
#[macro_export]
macro_rules! ffi_unwrap {
    ($res:expr, $msg:expr) => {
        match $res {
            Ok(v) => v,
            Err(e) => {
                $crate::error::set_last_error($crate::error::FfiError::Generic(format!(
                    "{}: {}",
                    $msg, e
                )));
                return -1_i32;
            }
        }
    };
    ($res:expr, $msg:expr, $code:expr) => {
        match $res {
            Ok(v) => v,
            Err(e) => {
                $crate::error::set_last_error($crate::error::FfiError::Generic(format!(
                    "{}: {}",
                    $msg, e
                )));
                return $code;
            }
        }
    };
}
```

This solution is backward compatible, does not mess the established conventions _and_ allows categorizing errors.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b16939d3..f0717d1d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,7 +15,7 @@ jobs:
       toolchain: stable
       kms-version: 4.9.1
       findex-cloud-version: 0.3.1
-      branch-java: develop
+      branch-java: fix/ffi_reserved_return_code
       branch-js: develop
       branch-kms-js: v2.0.0
       branch-flutter: develop
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 815045f6..09dc6877 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -38,6 +38,12 @@ repos:
       - id: dockerfile_lint
       - id: markdown-toc
 
+  - repo: https://github.com/tcort/markdown-link-check
+    rev: v3.11.2
+    hooks:
+      - id: markdown-link-check
+        args: [-q]
+
   - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
     rev: 0.2.2
     hooks:
@@ -115,6 +121,7 @@ repos:
   - repo: https://github.com/Cosmian/git-hooks.git
     rev: v1.0.24
     hooks:
+      # - id: dprint-toml-fix
       - id: stable-cargo-format
       - id: cargo-upgrade
       - id: cargo-update
diff --git a/Cargo.toml b/Cargo.toml
index e1960ee7..cd073a32 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,7 +16,8 @@ resolver = "1"
 async-trait = "0.1.74"
 base64 = "0.21.5"
 cosmian_crypto_core = { version = "9.3.0", default-features = false }
-cosmian_ffi_utils = "0.1.2"
+# cosmian_ffi_utils = "0.1.2" // TODO
+cosmian_ffi_utils = { path = "crates/ffi_utils" }
 hex = "0.4.3"
 js-sys = "0.3"
 pyo3 = { version = "0.20.0", features = [
@@ -31,7 +32,7 @@ tracing = "0.1"
 tracing-log = "0.1"
 tracing-subscriber = { version = "0.3", features = ["env-filter"] }
 tracing-tree = "0.2"
-wasm-bindgen = "0.2.88"
+wasm-bindgen = "0.2.89"
 
 [profile.release]
 lto = true
diff --git a/README.md b/README.md
index 447900f4..f4a2c2a6 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 
 Cloudproof Encryption provides libraries and tools to encrypt and securely index large repositories of data with advanced, high-performance security primitives with Post-Quantum resistance.
 
-See [the use cases and benefits](https://docs.cosmian.com/cloudproof_encryption/use_cases_benefits/) and a description of the [cryptosystems](https://docs.cosmian.com/cloudproof_encryption/crypto_systems/) used.
+See [an introduction to Cosmian libraries](https://docs.cosmian.com/cloudproof_encryption/how_it_works/) and a description of the [cryptosystems](https://docs.cosmian.com/cloudproof_encryption/crypto_systems/) used.
 
 The libraries are available in multiple languages to facilitate encryption close to the data source and decryption close to the decryption target, including mobile devices and browsers.
 
@@ -131,7 +131,7 @@ The `.whl` libraries can then be found in `target/wheels/`.
 
 ### Building the library for a different glibc
 
-Go to the [build](build/glibc-2.17/) directory for an example on how to build for GLIBC 2.17
+Go to the [build](glibc-2.17/) directory for an example on how to build for GLIBC 2.17
 
 ## Benchmarks
 
diff --git a/crates/aesgcm/Cargo.toml b/crates/aesgcm/Cargo.toml
index de3fbd17..1cd9aa4d 100644
--- a/crates/aesgcm/Cargo.toml
+++ b/crates/aesgcm/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cloudproof_aesgcm"
-version = "0.1.2"
+version = "0.1.3"
 authors = ["Emmanuel Coste<emmanuel.coste@cosmian.com>"]
 edition = "2021"
 license-file = "../../LICENSE.md"
@@ -8,7 +8,7 @@ repository = "https://github.com/cosmian/cloudproof_rust/"
 description = "Cosmian AES256GCM library"
 
 [lib]
-crate-type = ["cdylib", "rlib", "staticlib"]
+crate-type = ["cdylib", "lib", "staticlib"]
 name = "cloudproof_aesgcm"
 
 [features]
@@ -27,4 +27,4 @@ pyo3 = { workspace = true, optional = true }
 wasm-bindgen = { workspace = true, optional = true }
 
 [dev-dependencies]
-wasm-bindgen-test = "0.3.38"
+wasm-bindgen-test = "0.3.39"
diff --git a/crates/aesgcm/src/ffi/aesgcm.rs b/crates/aesgcm/src/ffi/aesgcm.rs
index f0042355..6c2dea4b 100644
--- a/crates/aesgcm/src/ffi/aesgcm.rs
+++ b/crates/aesgcm/src/ffi/aesgcm.rs
@@ -1,4 +1,4 @@
-use cosmian_ffi_utils::{ffi_read_bytes, ffi_unwrap, ffi_write_bytes};
+use cosmian_ffi_utils::{ffi_read_bytes, ffi_unwrap, ffi_write_bytes, ErrorCode};
 
 use crate::{decrypt, encrypt};
 
@@ -26,18 +26,18 @@ unsafe extern "C" fn aesgcm(
     let output = if encrypt_flag {
         ffi_unwrap!(
             encrypt(key_bytes, nonce_bytes, input_data_bytes, authenticated_data),
-            "AES-256 GCM encryption error"
+            "AES-256 GCM encryption error",
+            ErrorCode::Encryption
         )
     } else {
         ffi_unwrap!(
             decrypt(key_bytes, nonce_bytes, input_data_bytes, authenticated_data),
-            "AES-256 GCM decryption error"
+            "AES-256 GCM decryption error",
+            ErrorCode::Decryption
         )
     };
 
     ffi_write_bytes!("output_ptr", &output, output_ptr, output_len);
-
-    0
 }
 
 #[no_mangle]
diff --git a/crates/anonymization/Cargo.toml b/crates/anonymization/Cargo.toml
index bf61f52b..bfaf5487 100644
--- a/crates/anonymization/Cargo.toml
+++ b/crates/anonymization/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cloudproof_anonymization"
-version = "0.1.1"
+version = "0.1.2"
 authors = ["Hugo Rosenkranz-costa<hugo.rosenkranz@cosmian.com>"]
 edition = "2021"
 license-file = "../../LICENSE.md"
@@ -8,7 +8,7 @@ repository = "https://github.com/cosmian/cloudproof_rust/"
 description = "Cosmian Cloudproof Anonymization library"
 
 [lib]
-crate-type = ["cdylib", "rlib"]
+crate-type = ["cdylib", "lib", "staticlib"]
 name = "cloudproof_anonymization"
 
 [features]
@@ -43,4 +43,4 @@ wasm-bindgen = { workspace = true, optional = true }
 
 [dev-dependencies]
 approx = "0.5.1"
-wasm-bindgen-test = "0.3.38"
+wasm-bindgen-test = "0.3.39"
diff --git a/crates/cloudproof/Cargo.toml b/crates/cloudproof/Cargo.toml
index 919fc8a4..b274a470 100644
--- a/crates/cloudproof/Cargo.toml
+++ b/crates/cloudproof/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cloudproof"
-version = "2.3.0"
+version = "2.4.0"
 authors = [
   "Théophile Brézot<theophile.brezot@cosmian.com>",
   "Emmanuel Coste<emmanuel.coste@cosmian.com>",
@@ -14,7 +14,7 @@ repository = "https://github.com/cosmian/cloudproof_rust/"
 description = "Cosmian Cloudproof library"
 
 [lib]
-crate-type = ["cdylib", "rlib", "staticlib"]
+crate-type = ["cdylib", "lib", "staticlib"]
 name = "cloudproof"
 
 [features]
@@ -50,9 +50,9 @@ cloudproof_fpe = { path = "../fpe", optional = true }
 #  - to publish `cloudproof` package, we must publish those sub-crates individually and manually.
 #  - cbindgen follows the cargo deps and fetch the crates from crates.io.
 ######
-#cloudproof_aesgcm = { version = "0.1.2", optional = true }
-#cloudproof_anonymization = { version = "0.1.1", optional = true }
-#cloudproof_cover_crypt = { version = "13.0.0", optional = true }
-#cloudproof_ecies = { version = "0.1.2", optional = true }
-#cloudproof_findex = { version = "5.0.4", optional = true }
-#cloudproof_fpe = { version = "0.2.1", optional = true }
+#cloudproof_aesgcm = { version = "0.1.3", optional = true }
+#cloudproof_anonymization = { version = "0.1.2", optional = true }
+#cloudproof_cover_crypt = { version = "13.0.1", optional = true }
+#cloudproof_ecies = { version = "0.1.3", optional = true }
+#cloudproof_findex = { version = "6.0.0", optional = true }
+#cloudproof_fpe = { version = "0.2.2", optional = true }
diff --git a/crates/cover_crypt/Cargo.toml b/crates/cover_crypt/Cargo.toml
index 85c4414c..27d91961 100644
--- a/crates/cover_crypt/Cargo.toml
+++ b/crates/cover_crypt/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cloudproof_cover_crypt"
-version = "13.0.0"
+version = "13.0.1"
 authors = ["Théophile Brézot<theophile.brezot@cosmian.com>"]
 edition = "2021"
 license-file = "../../LICENSE.md"
@@ -8,7 +8,7 @@ repository = "https://github.com/cosmian/cloudproof_rust/"
 description = "Cosmian Covercrypt Cloudproof library"
 
 [lib]
-crate-type = ["cdylib", "rlib", "staticlib"]
+crate-type = ["cdylib", "lib", "staticlib"]
 name = "cloudproof_cover_crypt"
 
 [features]
@@ -31,4 +31,4 @@ wasm-bindgen = { workspace = true, optional = true }
 
 [dev-dependencies]
 cosmian_cover_crypt = { version = "13.0.0", features = ["test_utils"] }
-wasm-bindgen-test = "0.3.38"
+wasm-bindgen-test = "0.3.39"
diff --git a/crates/cover_crypt/src/ffi/abe_policy.rs b/crates/cover_crypt/src/ffi/cc_policy.rs
similarity index 92%
rename from crates/cover_crypt/src/ffi/abe_policy.rs
rename to crates/cover_crypt/src/ffi/cc_policy.rs
index 418a0393..27636cf9 100644
--- a/crates/cover_crypt/src/ffi/abe_policy.rs
+++ b/crates/cover_crypt/src/ffi/cc_policy.rs
@@ -1,5 +1,5 @@
 use cosmian_cover_crypt::abe_policy::{AccessPolicy, Attribute, EncryptionHint, Policy};
-use cosmian_ffi_utils::{ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes};
+use cosmian_ffi_utils::{ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes, ErrorCode};
 
 /// This macro handles deserializing the policy from JS, deserializing an
 /// attribute from JS, and performing a specified action on the policy. It also
@@ -39,26 +39,30 @@ macro_rules! update_policy {
             ffi_read_bytes!("current policy", $current_policy_ptr, $current_policy_len);
         let mut $cc_policy = ffi_unwrap!(
             Policy::parse_and_convert(policy_bytes),
-            "error deserializing policy"
+            "error deserializing policy",
+            ErrorCode::Serialization
         );
 
         let attr_string = ffi_read_string!("attribute", $attr_bytes);
         let $cc_attr = ffi_unwrap!(
             Attribute::try_from(attr_string.as_str()),
-            "error parsing attribute"
+            "error parsing attribute",
+            ErrorCode::InvalidArgument("Attribute".to_string())
         );
 
-        ffi_unwrap!($action, $error_msg);
+        ffi_unwrap!($action, $error_msg, ErrorCode::Serialization);
 
-        let policy_bytes =
-            ffi_unwrap!(<Vec<u8>>::try_from(&$cc_policy), "error serializing policy");
+        let policy_bytes = ffi_unwrap!(
+            <Vec<u8>>::try_from(&$cc_policy),
+            "error serializing policy",
+            ErrorCode::Serialization
+        );
         ffi_write_bytes!(
             "updated policy",
             &policy_bytes,
             $updated_policy_ptr,
             $updated_policy_len
         );
-        0
     }};
 }
 
@@ -66,9 +70,12 @@ macro_rules! update_policy {
 #[no_mangle]
 pub unsafe extern "C" fn h_policy(policy_ptr: *mut i8, policy_len: *mut i32) -> i32 {
     let policy = Policy::new();
-    let policy_bytes = ffi_unwrap!(<Vec<u8>>::try_from(&policy), "error deserializing policy");
+    let policy_bytes = ffi_unwrap!(
+        <Vec<u8>>::try_from(&policy),
+        "error deserializing policy",
+        ErrorCode::Serialization
+    );
     ffi_write_bytes!("policy", &policy_bytes, policy_ptr, policy_len);
-    0
 }
 
 /// # Safety
@@ -83,25 +90,33 @@ pub unsafe extern "C" fn h_add_policy_axis(
     let policy_bytes = ffi_read_bytes!("current policy", current_policy_ptr, current_policy_len);
     let mut policy = ffi_unwrap!(
         Policy::parse_and_convert(policy_bytes),
-        "error deserializing policy"
+        "error deserializing policy",
+        ErrorCode::Serialization
     );
     let axis_string = ffi_read_string!("axis", axis_ptr);
     let axis = ffi_unwrap!(
         serde_json::from_str(&axis_string),
-        "error deserializing policy axis"
+        "error deserializing policy axis",
+        ErrorCode::Serialization
     );
 
-    ffi_unwrap!(policy.add_dimension(axis), "error adding policy axis");
+    ffi_unwrap!(
+        policy.add_dimension(axis),
+        "error adding policy axis",
+        ErrorCode::CovercryptPolicy
+    );
 
-    let policy_bytes = ffi_unwrap!(<Vec<u8>>::try_from(&policy), "error serializing policy");
+    let policy_bytes = ffi_unwrap!(
+        <Vec<u8>>::try_from(&policy),
+        "error serializing policy",
+        ErrorCode::Serialization
+    );
     ffi_write_bytes!(
         "updated policy",
         &policy_bytes,
         updated_policy_ptr,
         updated_policy_len
     );
-
-    0
 }
 
 /// # Safety
@@ -116,25 +131,29 @@ pub unsafe extern "C" fn h_remove_policy_axis(
     let policy_bytes = ffi_read_bytes!("current policy", current_policy_ptr, current_policy_len);
     let mut policy = ffi_unwrap!(
         Policy::parse_and_convert(policy_bytes),
-        "error deserializing policy"
+        "error deserializing policy",
+        ErrorCode::Serialization
     );
 
     let axis_name = ffi_read_string!("axis name", axis_name_ptr);
 
     ffi_unwrap!(
         policy.remove_dimension(&axis_name),
-        "error removing policy axis"
+        "error removing policy axis",
+        ErrorCode::CovercryptPolicy
     );
 
-    let policy_bytes = ffi_unwrap!(<Vec<u8>>::try_from(&policy), "error serializing policy");
+    let policy_bytes = ffi_unwrap!(
+        <Vec<u8>>::try_from(&policy),
+        "error serializing policy",
+        ErrorCode::Serialization
+    );
     ffi_write_bytes!(
         "updated policy",
         &policy_bytes,
         updated_policy_ptr,
         updated_policy_len
     );
-
-    0
 }
 
 /// # Safety
@@ -278,7 +297,8 @@ pub unsafe extern "C" fn h_validate_boolean_expression(boolean_expression_ptr: *
     let boolean_expression = ffi_read_string!("boolean expression", boolean_expression_ptr);
     ffi_unwrap!(
         AccessPolicy::from_boolean_expression(&boolean_expression),
-        "error parsing boolean expression"
+        "error parsing boolean expression",
+        ErrorCode::Serialization
     );
     0
 }
@@ -289,7 +309,8 @@ pub unsafe extern "C" fn h_validate_attribute(attribute_ptr: *const i8) -> i32 {
     let attribute_str = ffi_read_string!("attribute", attribute_ptr);
     ffi_unwrap!(
         AccessPolicy::from_boolean_expression(&attribute_str),
-        "error parsing attribute"
+        "error parsing attribute",
+        ErrorCode::Serialization
     );
     0
 }
diff --git a/crates/cover_crypt/src/ffi/generate_cc_keys.rs b/crates/cover_crypt/src/ffi/generate_cc_keys.rs
index 1abc2a26..30dcb210 100644
--- a/crates/cover_crypt/src/ffi/generate_cc_keys.rs
+++ b/crates/cover_crypt/src/ffi/generate_cc_keys.rs
@@ -3,7 +3,7 @@ use cosmian_cover_crypt::{
     Covercrypt, MasterPublicKey, MasterSecretKey, UserSecretKey,
 };
 use cosmian_crypto_core::bytes_ser_de::Serializable;
-use cosmian_ffi_utils::{ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes};
+use cosmian_ffi_utils::{ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes, ErrorCode};
 
 #[no_mangle]
 /// Generates the master authority keys for supplied Policy.
@@ -25,15 +25,28 @@ pub unsafe extern "C" fn h_generate_master_keys(
     policy_len: i32,
 ) -> i32 {
     let policy_bytes = ffi_read_bytes!("policy", policy_ptr, policy_len);
-    let policy: Policy = ffi_unwrap!(Policy::try_from(policy_bytes), "error deserializing policy");
+    let policy: Policy = ffi_unwrap!(
+        Policy::try_from(policy_bytes),
+        "error deserializing policy",
+        ErrorCode::Serialization
+    );
 
     let (msk, mpk) = ffi_unwrap!(
         Covercrypt::default().generate_master_keys(&policy),
-        "error generating master keys"
+        "error generating master keys",
+        ErrorCode::Covercrypt
     );
 
-    let msk_bytes = ffi_unwrap!(msk.serialize(), "error serializing master secret key");
-    let mpk_bytes = ffi_unwrap!(mpk.serialize(), "error serializing public key");
+    let msk_bytes = ffi_unwrap!(
+        msk.serialize(),
+        "error serializing master secret key",
+        ErrorCode::Serialization
+    );
+    let mpk_bytes = ffi_unwrap!(
+        mpk.serialize(),
+        "error serializing public key",
+        ErrorCode::Serialization
+    );
     ffi_write_bytes!(
         "master secret key",
         &msk_bytes,
@@ -44,8 +57,6 @@ pub unsafe extern "C" fn h_generate_master_keys(
         mpk_ptr,
         mpk_len
     );
-
-    0
 }
 
 #[no_mangle]
@@ -71,28 +82,34 @@ pub unsafe extern "C" fn h_generate_user_secret_key(
     let msk_bytes = ffi_read_bytes!("master secret key", msk_ptr, msk_len);
     let msk = ffi_unwrap!(
         MasterSecretKey::deserialize(msk_bytes),
-        "error deserializing master secret key"
+        "error deserializing master secret key",
+        ErrorCode::Serialization
     );
     let policy_bytes = ffi_read_bytes!("policy", policy_ptr, policy_len);
     let policy = ffi_unwrap!(
         Policy::parse_and_convert(policy_bytes),
-        "error deserializing policy"
+        "error deserializing policy",
+        ErrorCode::Serialization
     );
     let user_policy_string = ffi_read_string!("access policy", user_policy_ptr);
     let user_policy = ffi_unwrap!(
         AccessPolicy::from_boolean_expression(user_policy_string.as_str()),
-        "error parsing user policy"
+        "error parsing user policy",
+        ErrorCode::Serialization
     );
 
     let usk = ffi_unwrap!(
         Covercrypt::default().generate_user_secret_key(&msk, &user_policy, &policy),
-        "error generating user secret key"
+        "error generating user secret key",
+        ErrorCode::Covercrypt
     );
 
-    let usk_bytes = ffi_unwrap!(usk.serialize(), "error serializing user secret key");
+    let usk_bytes = ffi_unwrap!(
+        usk.serialize(),
+        "error serializing user secret key",
+        ErrorCode::Serialization
+    );
     ffi_write_bytes!("user secret key", &usk_bytes, usk_ptr, usk_len);
-
-    0
 }
 
 #[no_mangle]
@@ -129,26 +146,38 @@ pub unsafe extern "C" fn h_update_master_keys(
     );
     let mut msk = ffi_unwrap!(
         MasterSecretKey::deserialize(msk_bytes),
-        "error deserializing master secret key"
+        "error deserializing master secret key",
+        ErrorCode::Serialization
     );
     let mpk_bytes = ffi_read_bytes!("current public key", current_mpk_ptr, current_mpk_len);
     let mut mpk = ffi_unwrap!(
         MasterPublicKey::deserialize(mpk_bytes),
-        "error deserializing public key"
+        "error deserializing public key",
+        ErrorCode::Serialization
     );
     let policy_bytes = ffi_read_bytes!("policy", policy_ptr, policy_len);
     let policy = ffi_unwrap!(
         Policy::parse_and_convert(policy_bytes),
-        "error deserializing policy"
+        "error deserializing policy",
+        ErrorCode::Serialization
     );
 
     ffi_unwrap!(
         Covercrypt::default().update_master_keys(&policy, &mut msk, &mut mpk),
-        "error updating master keys"
+        "error updating master keys",
+        ErrorCode::Covercrypt
     );
 
-    let msk_bytes = ffi_unwrap!(msk.serialize(), "error serializing master secret key");
-    let mpk_bytes = ffi_unwrap!(mpk.serialize(), "error serializing public key");
+    let msk_bytes = ffi_unwrap!(
+        msk.serialize(),
+        "error serializing master secret key",
+        ErrorCode::Serialization
+    );
+    let mpk_bytes = ffi_unwrap!(
+        mpk.serialize(),
+        "error serializing public key",
+        ErrorCode::Serialization
+    );
     ffi_write_bytes!(
         "updated master secret key",
         &msk_bytes,
@@ -159,8 +188,6 @@ pub unsafe extern "C" fn h_update_master_keys(
         updated_mpk_ptr,
         updated_mpk_len
     );
-
-    0
 }
 
 #[no_mangle]
@@ -199,22 +226,26 @@ pub unsafe extern "C" fn h_refresh_user_secret_key(
     let msk_bytes = ffi_read_bytes!("master secret key", msk_ptr, msk_len);
     let msk = ffi_unwrap!(
         MasterSecretKey::deserialize(msk_bytes),
-        "error deserializing master secret key"
+        "error deserializing master secret key",
+        ErrorCode::Serialization
     );
     let usk_bytes = ffi_read_bytes!("current user secret key", current_usk_ptr, current_usk_len);
     let mut usk = ffi_unwrap!(
         UserSecretKey::deserialize(usk_bytes),
-        "error deserializing user secret key"
+        "error deserializing user secret key",
+        ErrorCode::Serialization
     );
     let policy_bytes = ffi_read_bytes!("policy", policy_ptr, policy_len);
     let policy = ffi_unwrap!(
         Policy::parse_and_convert(policy_bytes),
-        "error deserializing policy"
+        "error deserializing policy",
+        ErrorCode::Serialization
     );
     let user_policy_string = ffi_read_string!("user policy", user_policy_ptr);
     let user_policy = ffi_unwrap!(
         AccessPolicy::from_boolean_expression(&user_policy_string),
-        "error parsing user policy"
+        "error parsing user policy",
+        ErrorCode::Serialization
     );
 
     ffi_unwrap!(
@@ -225,16 +256,19 @@ pub unsafe extern "C" fn h_refresh_user_secret_key(
             &policy,
             preserve_old_partitions_access != 0
         ),
-        "error refreshing user secret key"
+        "error refreshing user secret key",
+        ErrorCode::Covercrypt
     );
 
-    let usk_bytes = ffi_unwrap!(usk.serialize(), "error serializing user secret key");
+    let usk_bytes = ffi_unwrap!(
+        usk.serialize(),
+        "error serializing user secret key",
+        ErrorCode::Serialization
+    );
     ffi_write_bytes!(
         "updated user secret key",
         &usk_bytes,
         updated_usk_ptr,
         updated_usk_len
     );
-
-    0
 }
diff --git a/crates/cover_crypt/src/ffi/hybrid_cc_aes.rs b/crates/cover_crypt/src/ffi/hybrid_cc_aes.rs
index 965860a9..7bd55e76 100644
--- a/crates/cover_crypt/src/ffi/hybrid_cc_aes.rs
+++ b/crates/cover_crypt/src/ffi/hybrid_cc_aes.rs
@@ -14,7 +14,9 @@ use cosmian_crypto_core::{
     bytes_ser_de::{Deserializer, Serializable, Serializer},
     Aes256Gcm, FixedSizeCBytes, SymmetricKey,
 };
-use cosmian_ffi_utils::{ffi_bail, ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes};
+use cosmian_ffi_utils::{
+    ffi_bail, ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes, ErrorCode,
+};
 use lazy_static::lazy_static;
 
 // -------------------------------
@@ -52,11 +54,16 @@ pub unsafe extern "C" fn h_create_encryption_cache(
     mpk_len: i32,
 ) -> i32 {
     let policy = ffi_read_bytes!("policy", policy_ptr, policy_len);
-    let policy = ffi_unwrap!(Policy::try_from(policy), "error deserializing policy");
+    let policy = ffi_unwrap!(
+        Policy::try_from(policy),
+        "error deserializing policy",
+        ErrorCode::Serialization
+    );
     let mpk = ffi_read_bytes!("public key", mpk_ptr, mpk_len);
     let mpk = ffi_unwrap!(
         MasterPublicKey::deserialize(mpk),
-        "error deserializing public key"
+        "error deserializing public key",
+        ErrorCode::Serialization
     );
 
     let cache = EncryptionCache { policy, mpk };
@@ -103,7 +110,8 @@ pub unsafe extern "C" fn h_encrypt_header_using_cache(
     let encryption_policy_bytes = ffi_read_string!("encryption policy", encryption_policy_ptr);
     let encryption_policy = ffi_unwrap!(
         AccessPolicy::from_boolean_expression(&encryption_policy_bytes),
-        "error parsing encryption policy"
+        "error parsing encryption policy",
+        ErrorCode::CovercryptPolicy
     );
 
     let header_metadata = if header_metadata_ptr.is_null() || header_metadata_len == 0 {
@@ -146,12 +154,14 @@ pub unsafe extern "C" fn h_encrypt_header_using_cache(
             header_metadata,
             authentication_data,
         ),
-        "error encrypting CoverCrypt header"
+        "error encrypting CoverCrypt header",
+        ErrorCode::Covercrypt
     );
 
     let encrypted_header_bytes = ffi_unwrap!(
         encrypted_header.serialize(),
-        "error serializing encrypted CoverCrypt header"
+        "error serializing encrypted CoverCrypt header",
+        ErrorCode::Serialization
     );
 
     ffi_write_bytes!(
@@ -164,8 +174,6 @@ pub unsafe extern "C" fn h_encrypt_header_using_cache(
         header_bytes_ptr,
         header_bytes_len
     );
-
-    0
 }
 
 #[no_mangle]
@@ -190,16 +198,22 @@ pub unsafe extern "C" fn h_encrypt_header(
     authentication_data_len: i32,
 ) -> i32 {
     let policy = ffi_read_bytes!("policy", policy_ptr, policy_len);
-    let policy = ffi_unwrap!(Policy::try_from(policy), "error deserializing policy");
+    let policy = ffi_unwrap!(
+        Policy::try_from(policy),
+        "error deserializing policy",
+        ErrorCode::Serialization
+    );
     let mpk = ffi_read_bytes!("public key", mpk_ptr, mpk_len);
     let mpk = ffi_unwrap!(
         MasterPublicKey::deserialize(mpk),
-        "error deserializing public key"
+        "error deserializing public key",
+        ErrorCode::Serialization
     );
     let encryption_policy_string = ffi_read_string!("encryption policy", encryption_policy_ptr);
     let encryption_policy = ffi_unwrap!(
         AccessPolicy::from_boolean_expression(&encryption_policy_string),
-        "error parsing encryption policy"
+        "error parsing encryption policy",
+        ErrorCode::CovercryptPolicy
     );
     let header_metadata = if header_metadata_ptr.is_null() || header_metadata_len == 0 {
         None
@@ -230,12 +244,14 @@ pub unsafe extern "C" fn h_encrypt_header(
             header_metadata,
             authentication_data
         ),
-        "error encrypting CoverCrypt header"
+        "error encrypting CoverCrypt header",
+        ErrorCode::Encryption
     );
 
     let encrypted_header_bytes = ffi_unwrap!(
         encrypted_header.serialize(),
-        "error serializing encrypted CoverCrypt header"
+        "error serializing encrypted CoverCrypt header",
+        ErrorCode::Serialization
     );
 
     ffi_write_bytes!(
@@ -248,8 +264,6 @@ pub unsafe extern "C" fn h_encrypt_header(
         header_bytes_ptr,
         header_bytes_len
     );
-
-    0
 }
 
 // -------------------------------
@@ -286,7 +300,8 @@ pub unsafe extern "C" fn h_create_decryption_cache(
     let usk_bytes = ffi_read_bytes!("user secret key", usk_ptr, usk_len);
     let usk = ffi_unwrap!(
         UserSecretKey::deserialize(usk_bytes),
-        "error deserializing user secret key"
+        "error deserializing user secret key",
+        ErrorCode::Serialization
     );
 
     let cache = DecryptionCache { usk };
@@ -337,7 +352,8 @@ pub unsafe extern "C" fn h_decrypt_header_using_cache(
     );
     let encrypted_header = ffi_unwrap!(
         EncryptedHeader::deserialize(encrypted_header_bytes),
-        "error deserializing encrypted header"
+        "error deserializing encrypted header",
+        ErrorCode::Serialization
     );
     let authentication_data = if authentication_data_ptr.is_null() || authentication_data_len == 0 {
         None
@@ -362,7 +378,8 @@ pub unsafe extern "C" fn h_decrypt_header_using_cache(
 
     let header = ffi_unwrap!(
         encrypted_header.decrypt(&Covercrypt::default(), &cache.usk, authentication_data),
-        "error decrypting CoverCrypt header"
+        "error decrypting CoverCrypt header",
+        ErrorCode::Decryption
     );
 
     if header_metadata_ptr.is_null() {
@@ -386,8 +403,6 @@ pub unsafe extern "C" fn h_decrypt_header_using_cache(
             header_metadata_len
         );
     }
-
-    0
 }
 
 #[no_mangle]
@@ -412,7 +427,8 @@ pub unsafe extern "C" fn h_decrypt_header(
     let usk_bytes = ffi_read_bytes!("user secret key", usk_ptr, usk_len);
     let usk = ffi_unwrap!(
         UserSecretKey::deserialize(usk_bytes),
-        "error deserializing user secret key"
+        "error deserializing user secret key",
+        ErrorCode::Serialization
     );
     let encrypted_header_bytes = ffi_read_bytes!(
         "encrypted header",
@@ -421,7 +437,8 @@ pub unsafe extern "C" fn h_decrypt_header(
     );
     let encrypted_header = ffi_unwrap!(
         EncryptedHeader::deserialize(encrypted_header_bytes),
-        "encrypted header"
+        "encrypted header",
+        ErrorCode::Serialization
     );
 
     let authentication_data = if authentication_data_ptr.is_null() || authentication_data_len == 0 {
@@ -436,7 +453,8 @@ pub unsafe extern "C" fn h_decrypt_header(
 
     let decrypted_header = ffi_unwrap!(
         encrypted_header.decrypt(&Covercrypt::default(), &usk, authentication_data),
-        "error decrypting CoverCrypt header"
+        "error decrypting CoverCrypt header",
+        ErrorCode::Decryption
     );
 
     if header_metadata_ptr.is_null() {
@@ -460,8 +478,6 @@ pub unsafe extern "C" fn h_decrypt_header(
             header_metadata_len
         );
     }
-
-    0
 }
 
 #[no_mangle]
@@ -489,11 +505,13 @@ pub unsafe extern "C" fn h_dem_encrypt(
         ffi_read_bytes!("symmetric key", symmetric_key_ptr, symmetric_key_len);
     let symmetric_key_fixed_length = ffi_unwrap!(
         symmetric_key_bytes.try_into(),
-        "error converting to fixed length"
+        "error converting to fixed length",
+        ErrorCode::Serialization
     );
     let symmetric_key = ffi_unwrap!(
         SymmetricKey::try_from_bytes(symmetric_key_fixed_length),
-        "error parsing symmetric key"
+        "error parsing symmetric key",
+        ErrorCode::Serialization
     );
     let authentication_data = if authentication_data_ptr.is_null() || authentication_data_len == 0 {
         None
@@ -507,12 +525,11 @@ pub unsafe extern "C" fn h_dem_encrypt(
 
     let ciphertext = ffi_unwrap!(
         Covercrypt::default().encrypt(&symmetric_key, plaintext, authentication_data),
-        "error encrypting plaintext"
+        "error encrypting plaintext",
+        ErrorCode::Encryption
     );
 
     ffi_write_bytes!("ciphertext", &ciphertext, ciphertext_ptr, ciphertext_len);
-
-    0
 }
 
 #[no_mangle]
@@ -533,11 +550,13 @@ pub unsafe extern "C" fn h_dem_decrypt(
         ffi_read_bytes!("symmetric key", symmetric_key_ptr, symmetric_key_len);
     let symmetric_key_fixed_length = ffi_unwrap!(
         symmetric_key_bytes.try_into(),
-        "error converting to fixed length"
+        "error converting to fixed length",
+        ErrorCode::Serialization
     );
     let symmetric_key = ffi_unwrap!(
         SymmetricKey::try_from_bytes(symmetric_key_fixed_length),
-        "error parsing symmetric key"
+        "error parsing symmetric key",
+        ErrorCode::Serialization
     );
     let authentication_data = if authentication_data_ptr.is_null() || authentication_data_len == 0 {
         None
@@ -551,12 +570,11 @@ pub unsafe extern "C" fn h_dem_decrypt(
 
     let plaintext = ffi_unwrap!(
         Covercrypt::default().decrypt(&symmetric_key, ciphertext, authentication_data),
-        "error decrypting symmetric ciphertext"
+        "error decrypting symmetric ciphertext",
+        ErrorCode::Decryption
     );
 
     ffi_write_bytes!("plaintext", &plaintext, plaintext_ptr, plaintext_len);
-
-    0
 }
 
 #[no_mangle]
@@ -581,18 +599,21 @@ pub unsafe extern "C" fn h_hybrid_encrypt(
     let policy_bytes = ffi_read_bytes!("policy", policy_ptr, policy_len);
     let policy = ffi_unwrap!(
         Policy::parse_and_convert(policy_bytes),
-        "error deserializing policy"
+        "error deserializing policy",
+        ErrorCode::Serialization
     );
     let encryption_policy_string = ffi_read_string!("encryption policy", encryption_policy_ptr);
     let encryption_policy = ffi_unwrap!(
         AccessPolicy::from_boolean_expression(&encryption_policy_string),
-        "error parsing encryption policy"
+        "error parsing encryption policy",
+        ErrorCode::Serialization
     );
     let plaintext = ffi_read_bytes!("plaintext", plaintext_ptr, plaintext_len);
     let mpk_bytes = ffi_read_bytes!("public key", mpk_ptr, mpk_len);
     let mpk = ffi_unwrap!(
         MasterPublicKey::deserialize(mpk_bytes),
-        "error deserializing public key"
+        "error deserializing public key",
+        ErrorCode::Serialization
     );
     let header_metadata = if header_metadata_ptr.is_null() || header_metadata_len == 0 {
         None
@@ -623,28 +644,30 @@ pub unsafe extern "C" fn h_hybrid_encrypt(
             header_metadata,
             authentication_data
         ),
-        "error encrypting CoverCrypt header"
+        "error encrypting CoverCrypt header",
+        ErrorCode::Encryption
     );
 
     let ciphertext = ffi_unwrap!(
         Covercrypt::default().encrypt(&symmetric_key, plaintext, authentication_data,),
-        "error encrypting plaintext"
+        "error encrypting plaintext",
+        ErrorCode::Encryption
     );
 
     let mut ser = Serializer::with_capacity(encrypted_header.length() + ciphertext.len());
     ffi_unwrap!(
         ser.write(&encrypted_header),
-        "error serializing encrypted CoverCrypt header"
+        "error serializing encrypted CoverCrypt header",
+        ErrorCode::Serialization
     );
     ffi_unwrap!(
         ser.write_array(&ciphertext),
-        "error deserializing symmetric ciphertext"
+        "error deserializing symmetric ciphertext",
+        ErrorCode::Serialization
     );
     let bytes = ser.finalize();
 
     ffi_write_bytes!("ciphertext", &bytes, ciphertext_ptr, ciphertext_len);
-
-    0
 }
 
 #[no_mangle]
@@ -668,7 +691,8 @@ pub unsafe extern "C" fn h_hybrid_decrypt(
     let usk_bytes = ffi_read_bytes!("user secret key", usk_ptr, usk_len);
     let usk = ffi_unwrap!(
         UserSecretKey::deserialize(usk_bytes),
-        "error deserializing user secret key"
+        "error deserializing user secret key",
+        ErrorCode::Serialization
     );
     let authentication_data = if authentication_data_ptr.is_null() || authentication_data_len == 0 {
         None
@@ -685,7 +709,8 @@ pub unsafe extern "C" fn h_hybrid_decrypt(
     let encrypted_header = ffi_unwrap!(
         // this will read the exact header size
         de.read::<EncryptedHeader>(),
-        "error deserializing encrypted CoverCrypt header"
+        "error deserializing encrypted CoverCrypt header",
+        ErrorCode::Serialization
     );
     // the rest is the symmetric ciphertext
     let encrypted_content = de.finalize();
@@ -693,7 +718,8 @@ pub unsafe extern "C" fn h_hybrid_decrypt(
     // Decrypt header
     let decrypted_header = ffi_unwrap!(
         encrypted_header.decrypt(&Covercrypt::default(), &usk, authentication_data),
-        "error decrypting CoverCrypt header"
+        "error decrypting CoverCrypt header",
+        ErrorCode::Decryption
     );
 
     let plaintext = ffi_unwrap!(
@@ -702,7 +728,8 @@ pub unsafe extern "C" fn h_hybrid_decrypt(
             &encrypted_content,
             authentication_data,
         ),
-        "error decrypting symmetric ciphertext"
+        "error decrypting symmetric ciphertext",
+        ErrorCode::Decryption
     );
 
     if header_metadata_ptr.is_null() {
@@ -721,6 +748,4 @@ pub unsafe extern "C" fn h_hybrid_decrypt(
             header_metadata_len
         );
     }
-
-    0
 }
diff --git a/crates/cover_crypt/src/ffi/mod.rs b/crates/cover_crypt/src/ffi/mod.rs
index 394b820a..3e7a44fc 100644
--- a/crates/cover_crypt/src/ffi/mod.rs
+++ b/crates/cover_crypt/src/ffi/mod.rs
@@ -1,4 +1,4 @@
-mod abe_policy;
+mod cc_policy;
 mod generate_cc_keys;
 mod hybrid_cc_aes;
 
diff --git a/crates/ecies/Cargo.toml b/crates/ecies/Cargo.toml
index 89c0286d..45ac4416 100644
--- a/crates/ecies/Cargo.toml
+++ b/crates/ecies/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cloudproof_ecies"
-version = "0.1.2"
+version = "0.1.3"
 authors = ["Emmanuel Coste<emmanuel.coste@cosmian.com>"]
 edition = "2021"
 license-file = "../../LICENSE.md"
@@ -8,7 +8,7 @@ repository = "https://github.com/cosmian/cloudproof_rust/"
 description = "Cosmian ECIES scheme library"
 
 [lib]
-crate-type = ["cdylib", "rlib", "staticlib"]
+crate-type = ["cdylib", "lib", "staticlib"]
 name = "cloudproof_ecies"
 
 [features]
@@ -33,4 +33,4 @@ pyo3 = { workspace = true, optional = true }
 wasm-bindgen = { workspace = true, optional = true }
 
 [dev-dependencies]
-wasm-bindgen-test = "0.3.38"
+wasm-bindgen-test = "0.3.39"
diff --git a/crates/ecies/src/ffi/ecies.rs b/crates/ecies/src/ffi/ecies.rs
index 041307fb..6b2c8090 100644
--- a/crates/ecies/src/ffi/ecies.rs
+++ b/crates/ecies/src/ffi/ecies.rs
@@ -2,7 +2,7 @@ use cosmian_crypto_core::{
     reexport::rand_core::SeedableRng, CsRng, Ecies, EciesSalsaSealBox, FixedSizeCBytes,
     X25519PrivateKey, X25519PublicKey,
 };
-use cosmian_ffi_utils::{ffi_read_bytes, ffi_unwrap, ffi_write_bytes};
+use cosmian_ffi_utils::{ffi_read_bytes, ffi_unwrap, ffi_write_bytes, ErrorCode};
 
 #[no_mangle]
 pub unsafe extern "C" fn h_ecies_x25519_generate_key_pair(
@@ -25,8 +25,6 @@ pub unsafe extern "C" fn h_ecies_x25519_generate_key_pair(
         private_key_ptr,
         private_key_len
     );
-
-    0
 }
 
 unsafe extern "C" fn ecies_salsa_seal_box(
@@ -55,11 +53,13 @@ unsafe extern "C" fn ecies_salsa_seal_box(
             format!(
                 "ECIES error: public key length incorrect: expected {}",
                 X25519PublicKey::LENGTH
-            )
+            ),
+            ErrorCode::Serialization
         );
         let public_key = ffi_unwrap!(
             X25519PublicKey::try_from_bytes(public_key),
-            format!("ECIES error: public key deserializing")
+            format!("ECIES error: public key deserializing"),
+            ErrorCode::Serialization
         );
 
         ffi_unwrap!(
@@ -69,7 +69,8 @@ unsafe extern "C" fn ecies_salsa_seal_box(
                 input_data_bytes,
                 Some(authenticated_data_bytes)
             ),
-            "ECIES error: encryption"
+            "ECIES error: encryption",
+            ErrorCode::Encryption
         )
     } else {
         let private_key: [u8; X25519PrivateKey::LENGTH] = ffi_unwrap!(
@@ -77,11 +78,13 @@ unsafe extern "C" fn ecies_salsa_seal_box(
             format!(
                 "ECIES error: private key length incorrect: expected {}",
                 X25519PrivateKey::LENGTH
-            )
+            ),
+            ErrorCode::Serialization
         );
         let private_key = ffi_unwrap!(
             X25519PrivateKey::try_from_bytes(private_key),
-            format!("ECIES error: private key deserializing")
+            format!("ECIES error: private key deserializing"),
+            ErrorCode::Serialization
         );
 
         ffi_unwrap!(
@@ -90,12 +93,11 @@ unsafe extern "C" fn ecies_salsa_seal_box(
                 input_data_bytes,
                 Some(authenticated_data_bytes)
             ),
-            "ECIES error: decryption"
+            "ECIES error: decryption",
+            ErrorCode::Decryption
         )
     };
     ffi_write_bytes!("output_ptr", &output, output_ptr, output_len);
-
-    0
 }
 
 #[no_mangle]
diff --git a/crates/ffi_utils/Cargo.toml b/crates/ffi_utils/Cargo.toml
index a8976cf2..cb51aed1 100644
--- a/crates/ffi_utils/Cargo.toml
+++ b/crates/ffi_utils/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cosmian_ffi_utils"
-version = "0.1.2"
+version = "0.1.3"
 authors = ["Théophile Brézot<theophile.brezot@cosmian.com>"]
 edition = "2021"
 license-file = "../../LICENSE.md"
diff --git a/crates/ffi_utils/src/error.rs b/crates/ffi_utils/src/error.rs
index 48c3f648..fea3ef92 100644
--- a/crates/ffi_utils/src/error.rs
+++ b/crates/ffi_utils/src/error.rs
@@ -1,6 +1,8 @@
 use core::{cell::RefCell, fmt::Display};
 use std::ffi::CString;
 
+use crate::ErrorCode;
+
 #[derive(Debug)]
 pub enum FfiError {
     NullPointer(String),
@@ -77,12 +79,11 @@ pub unsafe extern "C" fn h_get_error(error_ptr: *mut i8, error_len: *mut i32) ->
     // Get the error message as a null terminated string.
     let cs = ffi_unwrap!(
         CString::new(get_last_error()),
-        "failed to convert error to CString"
+        "failed to convert error to CString",
+        ErrorCode::InvalidArgument("CString".to_string())
     );
 
     ffi_write_bytes!("error", cs.as_bytes(), error_ptr, error_len);
-
-    0
 }
 
 #[cfg(test)]
diff --git a/crates/ffi_utils/src/lib.rs b/crates/ffi_utils/src/lib.rs
index d742277a..c5dac8cc 100644
--- a/crates/ffi_utils/src/lib.rs
+++ b/crates/ffi_utils/src/lib.rs
@@ -1,4 +1,96 @@
+use std::fmt::Display;
+
 #[macro_use]
 pub mod macros;
 
 pub mod error;
+
+/// Error code for FFI code.
+#[derive(Debug, PartialEq, Eq)]
+pub enum ErrorCode {
+    Success,
+    BufferTooSmall,          // The output buffer is too small
+    MissingCallback,         // The callback needed does not exist
+    Serialization,           // An error occurred during (de)serialization
+    Backend,                 // The backend raised an error
+    InvalidArgument(String), // Invalid argument passed
+    Findex,                  // Findex call returned an error
+    Encryption,              // Encryption error
+    Decryption,              // Decryption error
+    Covercrypt,              // Covercrypt call returned an error
+    CovercryptPolicy,        // Error during Covercrypt Policy operation
+    Managed,                 // FFI client managed the error
+    Tokio,                   // Tokio runtime error
+    Fpe,                     // Format Preserving Encryption error
+    Ecies,                   // Ecies error
+    Unknown(i32),            // An unknown code was retrieved
+}
+
+impl From<ErrorCode> for i32 {
+    fn from(code: ErrorCode) -> Self {
+        match code {
+            ErrorCode::Success => 0,
+            ErrorCode::BufferTooSmall => 1,
+            ErrorCode::MissingCallback => 2,
+            ErrorCode::Serialization => 3,
+            ErrorCode::Backend => 4,
+            ErrorCode::InvalidArgument(_) => 5,
+            ErrorCode::Findex => 6,
+            ErrorCode::Encryption => 7,
+            ErrorCode::Decryption => 8,
+            ErrorCode::Covercrypt => 9,
+            ErrorCode::CovercryptPolicy => 10,
+            ErrorCode::Tokio => 11,
+            ErrorCode::Fpe => 12,
+            ErrorCode::Ecies => 13,
+            ErrorCode::Managed => 42,
+            ErrorCode::Unknown(code) => code,
+        }
+    }
+}
+
+impl From<i32> for ErrorCode {
+    fn from(value: i32) -> Self {
+        // FFI code can only be a success, a managed error or an unknown error code.
+        match value {
+            0 => Self::Success,
+            1 => Self::BufferTooSmall,
+            2 => Self::MissingCallback,
+            3 => Self::Serialization,
+            4 => Self::Backend,
+            6 => Self::Findex,
+            7 => Self::Encryption,
+            8 => Self::Decryption,
+            9 => Self::Covercrypt,
+            10 => Self::CovercryptPolicy,
+            11 => Self::Tokio,
+            12 => Self::Fpe,
+            13 => Self::Ecies,
+            42 => Self::Managed,
+            code => Self::Unknown(code),
+        }
+    }
+}
+
+impl Display for ErrorCode {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Success => write!(f, "Success"),
+            Self::BufferTooSmall => write!(f, "output buffer too small"),
+            Self::MissingCallback => write!(f, "missing callback"),
+            Self::Serialization => write!(f, "(de)serialization error"),
+            Self::Backend => write!(f, "backend error"),
+            Self::InvalidArgument(name) => write!(f, "invalid argument {name}"),
+            Self::Findex => write!(f, "findex call returned with error"),
+            Self::Encryption => write!(f, "encryption error"),
+            Self::Decryption => write!(f, "decryption error"),
+            Self::Covercrypt => write!(f, "covercrypt call returned with error"),
+            Self::CovercryptPolicy => write!(f, "covercrypt policy call returned with error"),
+            Self::Tokio => write!(f, "tokio error"),
+            Self::Fpe => write!(f, "format preserving encryption error"),
+            Self::Ecies => write!(f, "ecies error"),
+            Self::Managed => write!(f, "managed"),
+            Self::Unknown(code) => write!(f, "unknown code ({code})"),
+        }
+    }
+}
diff --git a/crates/ffi_utils/src/macros.rs b/crates/ffi_utils/src/macros.rs
index 902badd4..eb7358d9 100644
--- a/crates/ffi_utils/src/macros.rs
+++ b/crates/ffi_utils/src/macros.rs
@@ -11,7 +11,13 @@ macro_rules! ffi_not_null {
     ($name:literal, $ptr:expr) => {
         if $ptr.is_null() {
             $crate::error::set_last_error($crate::error::FfiError::NullPointer($name.to_string()));
-            return 1_i32;
+            return -1_i32;
+        }
+    };
+    ($name:literal, $ptr:expr, $code:expr) => {
+        if $ptr.is_null() {
+            $crate::error::set_last_error($crate::error::FfiError::NullPointer($name.to_string()));
+            return $code;
         }
     };
 }
@@ -19,13 +25,13 @@ macro_rules! ffi_not_null {
 /// Unwraps an `std::result::Result`.
 ///
 /// If the result is an error, sets the last error to this error and returns
-/// early with 1.
+/// early with -1 or the given error code.
 ///
 /// - `res` : result to unwrap
-/// - `msg` : (optional) additional message to use as error
+/// - `msg` : additional message to use as error
 #[macro_export]
 macro_rules! ffi_unwrap {
-    ($res:expr, $msg:expr) => {
+    ($res:expr, $msg:expr, $code:expr) => {
         match $res {
             Ok(v) => v,
             Err(e) => {
@@ -33,7 +39,7 @@ macro_rules! ffi_unwrap {
                     "{}: {}",
                     $msg, e
                 )));
-                return 1_i32;
+                return $code.into();
             }
         }
     };
@@ -50,15 +56,15 @@ macro_rules! ffi_unwrap {
 macro_rules! ffi_bail {
     ($msg:literal $(,)?) => {
         $crate::error::set_last_error($crate::error::FfiError::Generic($msg.to_owned()));
-        return 1_i32;
+        return -1_i32;
     };
     ($err:expr $(,)?) => {
         $crate::error::set_last_error($crate::error::FfiError::Generic($err.to_string()));
-        return 1_i32;
+        return -1_i32;
     };
     ($fmt:expr, $($arg:tt)*) => {
         $crate::error::set_last_error($crate::error::FfiError::Generic(format!($fmt, $($arg)*)));
-        return 1_i32;
+        return -1_i32;
     };
 }
 
@@ -95,13 +101,13 @@ macro_rules! ffi_write_bytes {
     ($($name: literal, $bytes: expr, $ptr: ident, $len: ident $(,)?)+) => {
 
         let mut error_code = 0_i32;
+        let mut nul_error = false;
 
-        // Write outputs one by one. Do not return on error, increment the
-        // `error_code` instead.
+        // Write outputs one by one. Do not return on error
         $(
             if $ptr.is_null() {
                 $crate::error::set_last_error($crate::error::FfiError::NullPointer($name.to_string()));
-                error_code += 1;
+                nul_error = true;
             } else {
                 let allocated = *$len;
                 *$len = $bytes.len() as i32;
@@ -110,7 +116,7 @@ macro_rules! ffi_write_bytes {
                         "The pre-allocated {} buffer is too small; need {} bytes, allocated {allocated}",
                         $name, *$len
                     )));
-                    error_code += 1;
+                    error_code = 1_i32;
                 } else {
                     std::slice::from_raw_parts_mut($ptr.cast(), $bytes.len()).copy_from_slice($bytes);
                 }
@@ -118,10 +124,9 @@ macro_rules! ffi_write_bytes {
 
         )+;
 
-        // Return here if there was an error. This allows for returning the
-        // correct size for several output buffers at once. The number of
-        // errors is returned.
-        if error_code > 0 {
+        if nul_error {
+            return -1_i32;
+        }else {
             return error_code;
         }
     };
diff --git a/crates/findex/Cargo.toml b/crates/findex/Cargo.toml
index f9ca1f09..53cd0d2a 100644
--- a/crates/findex/Cargo.toml
+++ b/crates/findex/Cargo.toml
@@ -14,7 +14,7 @@ repository = "https://github.com/cosmian/cloudproof_rust/"
 description = "Cosmian Findex Cloudproof library"
 
 [lib]
-crate-type = ["cdylib", "rlib", "staticlib"]
+crate-type = ["cdylib", "lib", "staticlib"]
 name = "cloudproof_findex"
 
 [features]
@@ -24,9 +24,9 @@ default = []
 # Meta features implicitly activated.
 serialization = []
 
-
 ffi = [
   "backend-ffi",
+  "backend-redis",
   "backend-rest",
   "cosmian_ffi_utils",
   "lazy_static",
@@ -69,15 +69,15 @@ backend-wasm = [
 ]
 
 [dependencies]
-async-trait = { workspace = true }
-cosmian_crypto_core = { workspace = true, default-features = false }
-cosmian_findex = { git = "https://github.com/Cosmian/findex", branch = "develop" }
 # Optional dependencies
 actix-rt = { version = "2.9", optional = true }
+async-trait = { workspace = true }
 base64 = { workspace = true, optional = true }
+cosmian_crypto_core = { workspace = true, default-features = false }
 cosmian_ffi_utils = { workspace = true, optional = true }
-js-sys = { workspace = true, optional = true }
+cosmian_findex = { git = "https://github.com/Cosmian/findex", branch = "develop" }
 futures = { version = "0.3.29", optional = true }
+js-sys = { workspace = true, optional = true }
 lazy_static = { version = "1.4.0", optional = true }
 log = { version = "0.4.20", optional = true }
 pyo3 = { workspace = true, optional = true }
@@ -96,7 +96,7 @@ tokio = { version = "1.34.0", optional = true }
 tracing = { workspace = true }
 tracing-subscriber = { workspace = true }
 wasm-bindgen = { workspace = true, optional = true }
-wasm-bindgen-futures = { version = "0.4.38", optional = true }
+wasm-bindgen-futures = { version = "0.4.39", optional = true }
 wasm-logger = { version = "0.2.0", optional = true }
 
 [dev-dependencies]
@@ -107,4 +107,4 @@ futures = "0.3.29"
 rand = { workspace = true }
 serde_json = "1.0.108"
 serial_test = "2.0.0"
-wasm-bindgen-test = "0.3.38"
+wasm-bindgen-test = "0.3.39"
diff --git a/crates/findex/python/requirements.txt b/crates/findex/python/requirements.txt
index a8c1bbee..ee246601 100644
--- a/crates/findex/python/requirements.txt
+++ b/crates/findex/python/requirements.txt
@@ -1,5 +1,5 @@
 maturin>=1.0,<2.0
 mypy>=1.0,<2.0
-requests
 redis
+requests
 types-requests
diff --git a/crates/findex/src/backends/custom/ffi/callbacks.rs b/crates/findex/src/backends/custom/ffi/callbacks.rs
index 7eab411c..bfc58e00 100644
--- a/crates/findex/src/backends/custom/ffi/callbacks.rs
+++ b/crates/findex/src/backends/custom/ffi/callbacks.rs
@@ -1,5 +1,6 @@
 //! Defines the FFI types for the callbacks used in Findex.
 
+use cosmian_ffi_utils::ErrorCode;
 use cosmian_findex::{Token, TokenToEncryptedValueMap, Tokens};
 use tracing::{debug, instrument, trace};
 
@@ -9,7 +10,6 @@ use crate::{
         deserialize_edx_lines, deserialize_token_set, get_serialized_edx_lines_size_bound,
         serialize_edx_lines, serialize_token_set,
     },
-    ErrorCode,
 };
 
 /// # Serialization
diff --git a/crates/findex/src/backends/error.rs b/crates/findex/src/backends/error.rs
index 373759f7..1333f697 100644
--- a/crates/findex/src/backends/error.rs
+++ b/crates/findex/src/backends/error.rs
@@ -2,6 +2,8 @@ use core::fmt::Display;
 use std::{array::TryFromSliceError, num::TryFromIntError};
 
 use cosmian_crypto_core::CryptoCoreError;
+#[cfg(feature = "backend-ffi")]
+use cosmian_ffi_utils::ErrorCode;
 use cosmian_findex::{CallbackErrorTrait, CoreError as FindexCoreError};
 #[cfg(feature = "backend-wasm")]
 use js_sys::{JsString, Object};
@@ -21,8 +23,6 @@ use wasm_bindgen::JsValue;
     feature = "ffi"
 ))]
 use crate::ser_de::SerializationError;
-#[cfg(feature = "backend-ffi")]
-use crate::ErrorCode;
 
 #[derive(Debug)]
 pub enum BackendError {
diff --git a/crates/findex/src/interfaces/ffi/api.rs b/crates/findex/src/interfaces/ffi/api.rs
index 4b41ba75..cad27014 100644
--- a/crates/findex/src/interfaces/ffi/api.rs
+++ b/crates/findex/src/interfaces/ffi/api.rs
@@ -12,7 +12,7 @@ use cosmian_crypto_core::{
 };
 use cosmian_ffi_utils::{
     error::{h_get_error, set_last_error, FfiError},
-    ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes,
+    ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes, ErrorCode,
 };
 use cosmian_findex::{
     Error as FindexError, IndexedValue, IndexedValueToKeywordsMap, Keyword, Keywords, Label,
@@ -36,7 +36,7 @@ use crate::{
         get_upsert_output_size, serialize_intermediate_results, serialize_keyword_set,
         serialize_location_set,
     },
-    BackendConfiguration, ErrorCode, InstantiatedFindex,
+    BackendConfiguration, InstantiatedFindex,
 };
 
 lazy_static! {
@@ -74,7 +74,8 @@ pub unsafe extern "C" fn h_instantiate_with_ffi_backend(
     let key_bytes = ffi_read_bytes!("key", key_ptr, key_len);
     let key = ffi_unwrap!(
         SymmetricKey::try_from_slice(key_bytes),
-        "error deserializing findex key"
+        "error deserializing findex key",
+        ErrorCode::Serialization
     );
     trace!("Key successfully parsed");
 
@@ -104,11 +105,13 @@ pub unsafe extern "C" fn h_instantiate_with_ffi_backend(
 
     let rt = ffi_unwrap!(
         tokio::runtime::Runtime::new(),
-        "error creating Tokio runtime"
+        "error creating Tokio runtime",
+        ErrorCode::Tokio
     );
     let findex = ffi_unwrap!(
         rt.block_on(InstantiatedFindex::new(config)),
-        "error instantiating Findex with custom backend"
+        "error instantiating Findex with custom backend",
+        ErrorCode::Findex
     );
 
     let mut cache = FINDEX_INSTANCES
@@ -116,7 +119,8 @@ pub unsafe extern "C" fn h_instantiate_with_ffi_backend(
         .expect("Findex instance cache lock poisoned.");
     let handle = ffi_unwrap!(
         <i32>::try_from(cache.len()),
-        "findex instance cache capacity overflow"
+        "findex instance cache capacity overflow",
+        ErrorCode::Findex
     );
 
     cache.insert(handle, (key, label, findex));
@@ -157,7 +161,8 @@ pub unsafe extern "C" fn h_instantiate_with_rest_backend(
     trace!("Authorization token read: {token}");
     let authorization_token = ffi_unwrap!(
         crate::backends::rest::AuthorizationToken::from_str(&token),
-        "authorization token conversion failed"
+        "authorization token conversion failed",
+        ErrorCode::Backend
     );
 
     let base_url = if url_ptr.is_null() {
@@ -170,11 +175,13 @@ pub unsafe extern "C" fn h_instantiate_with_rest_backend(
 
     let rt = ffi_unwrap!(
         tokio::runtime::Runtime::new(),
-        "error creating Tokio runtime"
+        "error creating Tokio runtime",
+        ErrorCode::Tokio
     );
     let findex = ffi_unwrap!(
         rt.block_on(InstantiatedFindex::new(config)),
-        "error instantiating Findex with REST backend"
+        "error instantiating Findex with REST backend",
+        ErrorCode::Backend
     );
 
     let mut cache = FINDEX_INSTANCES
@@ -182,7 +189,8 @@ pub unsafe extern "C" fn h_instantiate_with_rest_backend(
         .expect("Findex instance cache lock poisoned.");
     let handle = ffi_unwrap!(
         <i32>::try_from(cache.len()),
-        "findex instance cache capacity overflow"
+        "findex instance cache capacity overflow",
+        ErrorCode::Findex
     );
     cache.insert(handle, (authorization_token.findex_key, label, findex));
 
@@ -191,6 +199,77 @@ pub unsafe extern "C" fn h_instantiate_with_rest_backend(
     ErrorCode::Success.into()
 }
 
+/// Instantiate a Findex using a Redis backend.
+///
+/// # Parameters
+///
+/// - `key`     : findex key
+/// - `label`   : label used by Findex
+/// - `entry_table_redis_url`     : Redis entry table URL
+/// - `chain_table_redis_url`     : Redis chain table URL
+///
+/// # Safety
+///
+/// Cannot be safe since using FFI.
+#[no_mangle]
+#[tracing::instrument(ret, skip_all)]
+pub unsafe extern "C" fn h_instantiate_with_redis_backend(
+    findex_handle: *mut i32,
+    key_ptr: *const u8,
+    key_len: i32,
+    label_ptr: *const u8,
+    label_len: i32,
+    entry_table_redis_url_ptr: *const i8,
+    chain_table_redis_url_ptr: *const i8,
+) -> i32 {
+    #[cfg(debug_assertions)]
+    log_init();
+
+    let key_bytes = ffi_read_bytes!("key", key_ptr, key_len);
+    let key = ffi_unwrap!(
+        SymmetricKey::try_from_slice(key_bytes),
+        "error deserializing findex key",
+        ErrorCode::Serialization
+    );
+    trace!("Key successfully parsed");
+
+    let label_bytes = ffi_read_bytes!("label", label_ptr, label_len);
+    let label = Label::from(label_bytes);
+    trace!("Label successfully parsed: label: {label}");
+
+    let entry_table_redis_url =
+        ffi_read_string!("Redis entry table URL", entry_table_redis_url_ptr);
+    let chain_table_redis_url =
+        ffi_read_string!("Redis chain table URL", chain_table_redis_url_ptr);
+
+    let config = BackendConfiguration::Redis(entry_table_redis_url, chain_table_redis_url);
+
+    let rt = ffi_unwrap!(
+        tokio::runtime::Runtime::new(),
+        "error creating Tokio runtime",
+        ErrorCode::Tokio
+    );
+    let findex = ffi_unwrap!(
+        rt.block_on(InstantiatedFindex::new(config)),
+        "error instantiating Findex with REST backend",
+        ErrorCode::Findex
+    );
+
+    let mut cache = FINDEX_INSTANCES
+        .lock()
+        .expect("Findex instance cache lock poisoned.");
+    let handle = ffi_unwrap!(
+        <i32>::try_from(cache.len()),
+        "findex instance cache capacity overflow",
+        ErrorCode::Findex
+    );
+    cache.insert(handle, (key, label, findex));
+
+    *findex_handle = handle;
+
+    ErrorCode::Success.into()
+}
+
 /// Searches the index for the given keywords.
 ///
 /// At each search recursion, the passed `interrupt` function is called with the
@@ -222,7 +301,8 @@ pub unsafe extern "C" fn h_search(
 
     let keywords = ffi_unwrap!(
         deserialize_keyword_set(ffi_read_bytes!("keywords", keywords_ptr, keywords_len)),
-        "error deserializing keywords"
+        "error deserializing keywords",
+        ErrorCode::Serialization
     );
     let keywords = Keywords::from(keywords);
     trace!("Keywords successfully parsed: keywords: {keywords}");
@@ -243,12 +323,14 @@ pub unsafe extern "C" fn h_search(
         cache
             .get(&findex_handle)
             .ok_or_else(|| format!("no matching instance for handle {findex_handle}")),
-        "cannot get a hold on the Findex instance"
+        "cannot get a hold on the Findex instance",
+        ErrorCode::Findex
     );
 
     let rt = ffi_unwrap!(
         tokio::runtime::Runtime::new(),
-        "error creating Tokio runtime"
+        "error creating Tokio runtime",
+        ErrorCode::Tokio
     );
 
     let res = rt.block_on(findex.search(key, label, keywords, &user_interrupt));
@@ -274,22 +356,29 @@ pub unsafe extern "C" fn h_search(
     let mut serializer = Serializer::new();
     ffi_unwrap!(
         serializer.write_leb128_u64(results.len() as u64),
-        "error serializing length"
+        "error serializing length",
+        ErrorCode::Serialization
     );
     for (keyword, locations) in results {
-        ffi_unwrap!(serializer.write_vec(&keyword), "error serializing keyword");
-        let serialized_location_set =
-            ffi_unwrap!(serialize_location_set(&locations), "error serializing set");
+        ffi_unwrap!(
+            serializer.write_vec(&keyword),
+            "error serializing keyword",
+            ErrorCode::Serialization
+        );
+        let serialized_location_set = ffi_unwrap!(
+            serialize_location_set(&locations),
+            "error serializing set",
+            ErrorCode::Serialization
+        );
         ffi_unwrap!(
             serializer.write_array(&serialized_location_set),
-            "error serializing locations"
+            "error serializing locations",
+            ErrorCode::Serialization
         );
     }
     let serialized_uids = serializer.finalize();
 
     ffi_write_bytes!("search results", &serialized_uids, results_ptr, results_len);
-
-    ErrorCode::Success.into()
 }
 
 /// Adds the given associations to the index.
@@ -318,7 +407,8 @@ pub unsafe extern "C" fn h_add(
     let associations_bytes = ffi_read_bytes!("associations", associations_ptr, associations_len);
     let associations = IndexedValueToKeywordsMap::from(ffi_unwrap!(
         deserialize_indexed_values(associations_bytes),
-        "failed deserialize indexed values (associations)"
+        "failed deserialize indexed values (associations)",
+        ErrorCode::Serialization
     ));
 
     let output_size = get_upsert_output_size(&associations);
@@ -339,14 +429,16 @@ pub unsafe extern "C" fn h_add(
         cache
             .get(&findex_handle)
             .ok_or_else(|| format!("no matching instance for handle {findex_handle}")),
-        "cannot get a hold on the Findex instance"
+        "cannot get a hold on the Findex instance",
+        ErrorCode::Findex
     );
 
     trace!("instantiated Findex: {findex:?}");
 
     let rt = ffi_unwrap!(
         tokio::runtime::Runtime::new(),
-        "error creating Tokio runtime"
+        "error creating Tokio runtime",
+        ErrorCode::Tokio
     );
 
     let res = rt.block_on(findex.add(key, label, associations));
@@ -368,7 +460,8 @@ pub unsafe extern "C" fn h_add(
     // Serialize the results.
     let serialized_keywords = ffi_unwrap!(
         serialize_keyword_set(&new_keywords),
-        "serialize new keywords"
+        "serialize new keywords",
+        ErrorCode::Serialization
     );
 
     ffi_write_bytes!(
@@ -377,8 +470,6 @@ pub unsafe extern "C" fn h_add(
         results_ptr,
         results_len
     );
-
-    ErrorCode::Success.into()
 }
 
 /// Removes the given associations from the index.
@@ -407,7 +498,8 @@ pub unsafe extern "C" fn h_delete(
     let associations_bytes = ffi_read_bytes!("associations", associations_ptr, associations_len);
     let associations = IndexedValueToKeywordsMap::from(ffi_unwrap!(
         deserialize_indexed_values(associations_bytes),
-        "failed deserialize indexed values (associations)"
+        "failed deserialize indexed values (associations)",
+        ErrorCode::Serialization
     ));
 
     let output_size = get_upsert_output_size(&associations);
@@ -428,14 +520,16 @@ pub unsafe extern "C" fn h_delete(
         cache
             .get(&findex_handle)
             .ok_or_else(|| format!("no matching instance for handle {findex_handle}")),
-        "cannot get a hold on the Findex instance"
+        "cannot get a hold on the Findex instance",
+        ErrorCode::Findex
     );
 
     trace!("instantiated Findex: {findex:?}");
 
     let rt = ffi_unwrap!(
         tokio::runtime::Runtime::new(),
-        "error creating Tokio runtime"
+        "error creating Tokio runtime",
+        ErrorCode::Tokio
     );
 
     let res = rt.block_on(findex.delete(key, label, associations));
@@ -457,7 +551,8 @@ pub unsafe extern "C" fn h_delete(
     // Serialize the results.
     let serialized_keywords = ffi_unwrap!(
         serialize_keyword_set(&new_keywords),
-        "serialize new keywords"
+        "serialize new keywords",
+        ErrorCode::Serialization
     );
 
     ffi_write_bytes!(
@@ -466,8 +561,6 @@ pub unsafe extern "C" fn h_delete(
         results_ptr,
         results_len
     );
-
-    ErrorCode::Success.into()
 }
 
 /// Replaces all the Index Entry Table UIDs and values. New UIDs are derived
@@ -537,7 +630,8 @@ pub unsafe extern "C" fn h_compact(
     let new_key_bytes = ffi_read_bytes!("new key", new_key_ptr, new_key_len);
     let new_key = ffi_unwrap!(
         SymmetricKey::try_from_slice(new_key_bytes),
-        "error deserializing new findex key"
+        "error deserializing new findex key",
+        ErrorCode::Serialization
     );
 
     let new_label_bytes = ffi_read_bytes!("new label", new_label_ptr, new_label_len);
@@ -551,12 +645,14 @@ pub unsafe extern "C" fn h_compact(
         cache
             .get_mut(&findex_handle)
             .ok_or_else(|| format!("no matching instance for handle {findex_handle}")),
-        "cannot get a hold on the Findex instance"
+        "cannot get a hold on the Findex instance",
+        ErrorCode::Findex
     );
 
     let rt = ffi_unwrap!(
         tokio::runtime::Runtime::new(),
-        "error creating Tokio runtime"
+        "error creating Tokio runtime",
+        ErrorCode::Tokio
     );
 
     trace!("instantiated Findex: {findex:?}");
@@ -644,28 +740,32 @@ pub unsafe extern "C" fn h_generate_new_token(
         CallbackPrefix::FetchEntry,
         ffi_unwrap!(
             SymmetricKey::try_from_slice(fetch_entries_seed),
-            "fetch_entries_seed is of wrong size"
+            "fetch_entries_seed is of wrong size",
+            ErrorCode::Serialization
         ),
     );
     seeds.insert(
         CallbackPrefix::FetchChain,
         ffi_unwrap!(
             SymmetricKey::try_from_slice(fetch_chains_seed),
-            "fetch_chains_seed is of wrong size"
+            "fetch_chains_seed is of wrong size",
+            ErrorCode::Serialization
         ),
     );
     seeds.insert(
         CallbackPrefix::Upsert,
         ffi_unwrap!(
             SymmetricKey::try_from_slice(upsert_entries_seed),
-            "upsert_entries_seed is of wrong size"
+            "upsert_entries_seed is of wrong size",
+            ErrorCode::Serialization
         ),
     );
     seeds.insert(
         CallbackPrefix::Insert,
         ffi_unwrap!(
             SymmetricKey::try_from_slice(insert_chains_seed),
-            "insert_chains_seed is of wrong size"
+            "insert_chains_seed is of wrong size",
+            ErrorCode::Serialization
         ),
     );
 
@@ -674,7 +774,8 @@ pub unsafe extern "C" fn h_generate_new_token(
 
     let token = ffi_unwrap!(
         AuthorizationToken::new(index_id, findex_key, seeds),
-        "generate authorization token"
+        "generate authorization token",
+        ErrorCode::Findex
     );
 
     ffi_write_bytes!(
@@ -683,8 +784,6 @@ pub unsafe extern "C" fn h_generate_new_token(
         token_ptr,
         token_len
     );
-
-    ErrorCode::Success.into()
 }
 
 /// Re-export the `cosmian_ffi` `h_get_error` function to clients with the old
diff --git a/crates/findex/src/lib.rs b/crates/findex/src/lib.rs
index 29617b44..257eae05 100644
--- a/crates/findex/src/lib.rs
+++ b/crates/findex/src/lib.rs
@@ -21,9 +21,6 @@ pub mod logger;
 #[cfg(feature = "serialization")]
 pub mod ser_de;
 
-#[cfg(any(feature = "backend-ffi", feature = "ffi"))]
-use std::fmt::Display;
-
 #[cfg(any(
     feature = "backend-ffi",
     feature = "backend-python",
@@ -33,67 +30,3 @@ use std::fmt::Display;
     feature = "backend-wasm",
 ))]
 pub use instantiation::{BackendConfiguration, InstantiatedFindex};
-
-/// Error code returned by the callbacks.
-///
-/// They can be generated either by the Rust code or the FFI backend.
-#[cfg(any(feature = "backend-ffi", feature = "ffi"))]
-#[derive(Debug, PartialEq, Eq)]
-pub enum ErrorCode {
-    Success,
-    BufferTooSmall,          // The output buffer is too small
-    MissingCallback,         // The callback needed does not exist
-    Serialization,           // An error occurred during serialization
-    Backend,                 // The backend raised an error
-    InvalidArgument(String), // Invalid argument passed
-    Findex,                  // Findex call returned an error
-    Managed,                 // FFI client managed the error
-    Unknown(i32),            // An unknown code was retrieved
-}
-
-#[cfg(any(feature = "backend-ffi", feature = "ffi"))]
-impl From<ErrorCode> for i32 {
-    fn from(code: ErrorCode) -> Self {
-        match code {
-            ErrorCode::Success => 0,
-            ErrorCode::BufferTooSmall => 1,
-            ErrorCode::MissingCallback => 2,
-            ErrorCode::Serialization => 3,
-            ErrorCode::Backend => 4,
-            ErrorCode::InvalidArgument(_) => 5,
-            ErrorCode::Findex => 6,
-            ErrorCode::Managed => 42,
-            ErrorCode::Unknown(_) => 43,
-        }
-    }
-}
-
-#[cfg(any(feature = "backend-ffi", feature = "ffi"))]
-impl From<i32> for ErrorCode {
-    fn from(value: i32) -> Self {
-        // FFI code can only be a success, a managed error or an unknown error code.
-        match value {
-            0 => Self::Success,
-            1 => Self::BufferTooSmall,
-            42 => Self::Managed,
-            code => Self::Unknown(code),
-        }
-    }
-}
-
-#[cfg(any(feature = "backend-ffi", feature = "ffi"))]
-impl Display for ErrorCode {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::Success => write!(f, "Success"),
-            Self::BufferTooSmall => write!(f, "output buffer too small"),
-            Self::MissingCallback => write!(f, "missing callback"),
-            Self::Serialization => write!(f, "serialization error"),
-            Self::Backend => write!(f, "backend error"),
-            Self::InvalidArgument(name) => write!(f, "invalid argument {name}"),
-            Self::Findex => write!(f, "findex call returned with error"),
-            Self::Managed => write!(f, "managed"),
-            Self::Unknown(code) => write!(f, "unknown code ({code})"),
-        }
-    }
-}
diff --git a/crates/fpe/Cargo.toml b/crates/fpe/Cargo.toml
index 22da4067..fcbb9eab 100644
--- a/crates/fpe/Cargo.toml
+++ b/crates/fpe/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cloudproof_fpe"
-version = "0.2.1"
+version = "0.2.2"
 authors = ["Théophile Brézot<theophile.brezot@cosmian.com>"]
 edition = "2021"
 license-file = "../../LICENSE.md"
@@ -8,7 +8,7 @@ repository = "https://github.com/cosmian/cloudproof_rust/"
 description = "Cosmian Cloudproof FPE library"
 
 [lib]
-crate-type = ["cdylib", "rlib"]
+crate-type = ["cdylib", "lib", "staticlib"]
 name = "cloudproof_fpe"
 
 [[bench]]
@@ -41,4 +41,4 @@ getrandom = { version = "0.2", features = [
 rand = "0.8"
 rand_chacha = "0.3"
 rand_distr = "0.4"
-wasm-bindgen-test = "0.3.38"
+wasm-bindgen-test = "0.3.39"
diff --git a/crates/fpe/src/ffi/alphabet.rs b/crates/fpe/src/ffi/alphabet.rs
index d2477a94..dac855b9 100644
--- a/crates/fpe/src/ffi/alphabet.rs
+++ b/crates/fpe/src/ffi/alphabet.rs
@@ -1,4 +1,4 @@
-use cosmian_ffi_utils::{ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes};
+use cosmian_ffi_utils::{ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes, ErrorCode};
 
 use crate::get_alphabet;
 
@@ -20,7 +20,11 @@ pub unsafe fn fpe(
     let input_str = ffi_read_string!("input", input_ptr);
     let alphabet_id_str = ffi_read_string!("alphabet_id", alphabet_id_ptr);
 
-    let mut alphabet = ffi_unwrap!(get_alphabet(&alphabet_id_str), "Alphabet id not supported");
+    let mut alphabet = ffi_unwrap!(
+        get_alphabet(&alphabet_id_str),
+        "Alphabet id not supported",
+        ErrorCode::Fpe
+    );
     let additional_characters_str =
         ffi_read_string!("additional_characters_ptr", additional_characters_ptr);
     alphabet.extend_with(&additional_characters_str);
@@ -28,18 +32,18 @@ pub unsafe fn fpe(
     let output_str = if encrypt_flag {
         ffi_unwrap!(
             alphabet.encrypt(key_bytes, tweak_bytes, &input_str),
-            "fpe encryption process"
+            "fpe encryption process",
+            ErrorCode::Encryption
         )
     } else {
         ffi_unwrap!(
             alphabet.decrypt(key_bytes, tweak_bytes, &input_str),
-            "fpe decryption process"
+            "fpe decryption process",
+            ErrorCode::Decryption
         )
     };
 
     ffi_write_bytes!("output_ptr", output_str.as_bytes(), output_ptr, output_len);
-
-    0
 }
 
 /// Encrypts a string using Format Preserving Encryption (FPE) algorithm with
diff --git a/crates/fpe/src/ffi/float.rs b/crates/fpe/src/ffi/float.rs
index 263d0caf..1f36521f 100644
--- a/crates/fpe/src/ffi/float.rs
+++ b/crates/fpe/src/ffi/float.rs
@@ -1,4 +1,4 @@
-use cosmian_ffi_utils::{ffi_read_bytes, ffi_unwrap};
+use cosmian_ffi_utils::{ffi_read_bytes, ffi_unwrap, ErrorCode};
 
 use crate::core::{Float, KEY_LENGTH};
 
@@ -15,16 +15,28 @@ unsafe extern "C" fn fpe(
     let tweak_bytes = ffi_read_bytes!("tweak", tweak_ptr, tweak_len);
 
     // Copy the contents of the slice into the 32-array
-    let key: [u8; KEY_LENGTH] = ffi_unwrap!(key_bytes.try_into(), "key size is 32 bytes");
+    let key: [u8; KEY_LENGTH] = ffi_unwrap!(
+        key_bytes.try_into(),
+        "key size is 32 bytes",
+        ErrorCode::Serialization
+    );
 
-    let itg = ffi_unwrap!(Float::instantiate(), "cannot instantiate FPE float");
+    let itg = ffi_unwrap!(
+        Float::instantiate(),
+        "cannot instantiate FPE float",
+        ErrorCode::Fpe
+    );
 
     let operation = match encrypt_flag {
         true => itg.encrypt(&key, tweak_bytes, input),
         false => itg.decrypt(&key, tweak_bytes, input),
     };
 
-    *output = ffi_unwrap!(operation, "fpe encryption/decryption process");
+    *output = ffi_unwrap!(
+        operation,
+        "fpe encryption/decryption process",
+        ErrorCode::Fpe
+    );
 
     0
 }
diff --git a/crates/fpe/src/ffi/integer.rs b/crates/fpe/src/ffi/integer.rs
index e6e6d037..eaf840bd 100644
--- a/crates/fpe/src/ffi/integer.rs
+++ b/crates/fpe/src/ffi/integer.rs
@@ -1,4 +1,4 @@
-use cosmian_ffi_utils::{ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes};
+use cosmian_ffi_utils::{ffi_read_bytes, ffi_read_string, ffi_unwrap, ffi_write_bytes, ErrorCode};
 use num_bigint::BigUint;
 use num_traits::Num;
 
@@ -19,22 +19,29 @@ unsafe extern "C" fn fpe(
     let tweak_bytes = ffi_read_bytes!("tweak", tweak_ptr, tweak_len);
 
     // Copy the contents of the slice into the 32-array
-    let key: [u8; KEY_LENGTH] = ffi_unwrap!(key_bytes.try_into(), "key size is 32 bytes");
+    let key: [u8; KEY_LENGTH] = ffi_unwrap!(
+        key_bytes.try_into(),
+        "key size is 32 bytes",
+        ErrorCode::Serialization
+    );
 
     let itg = ffi_unwrap!(
         Integer::instantiate(radix, digits as usize),
-        "cannot instantiate FPE integer"
+        "cannot instantiate FPE integer",
+        ErrorCode::Fpe
     );
 
     *output = if encrypt_flag {
         ffi_unwrap!(
             itg.encrypt(&key, tweak_bytes, input),
-            "fpe encryption process"
+            "fpe encryption process",
+            ErrorCode::Encryption
         )
     } else {
         ffi_unwrap!(
             itg.decrypt(&key, tweak_bytes, input),
-            "fpe decryption process"
+            "fpe decryption process",
+            ErrorCode::Decryption
         )
     };
 
@@ -139,34 +146,39 @@ unsafe extern "C" fn fpe_big_integer(
 
     let itg = ffi_unwrap!(
         Integer::instantiate(radix, digits as usize),
-        "cannot instantiate FPE integer"
+        "cannot instantiate FPE integer",
+        ErrorCode::Fpe
     );
     let input_biguint = ffi_unwrap!(
         BigUint::from_str_radix(&input_str, radix),
-        "failed to convert
-input to BigUint"
+        "failed to convert input to BigUint",
+        ErrorCode::Serialization
     );
 
     // Copy the contents of the slice into the 32-array
-    let key: [u8; KEY_LENGTH] = ffi_unwrap!(key_bytes.try_into(), "key size is 32 bytes");
+    let key: [u8; KEY_LENGTH] = ffi_unwrap!(
+        key_bytes.try_into(),
+        "key size is 32 bytes",
+        ErrorCode::Serialization
+    );
 
     let output = if encrypt_flag {
         ffi_unwrap!(
             itg.encrypt_big(&key, tweak_bytes, &input_biguint),
-            "fpe encryption process"
+            "fpe encryption process",
+            ErrorCode::Encryption
         )
     } else {
         ffi_unwrap!(
             itg.decrypt_big(&key, tweak_bytes, &input_biguint),
-            "fpe decryption process"
+            "fpe decryption process",
+            ErrorCode::Decryption
         )
     };
 
     let output_str = output.to_str_radix(radix);
 
     ffi_write_bytes!("output_ptr", output_str.as_bytes(), output_ptr, output_len);
-
-    0
 }
 
 /// Encrypts an input big integer using the FPE algorithm and returns the


Test patch:
diff --git a/crates/findex/python/tests/findex_test.py b/crates/findex/python/tests/findex_test.py
index 07615d90..3efdea87 100644
--- a/crates/findex/python/tests/findex_test.py
+++ b/crates/findex/python/tests/findex_test.py
@@ -207,7 +207,7 @@ def define_custom_backends(is_with_test: bool = False):
     def fetch(uids, table: dict):
         res = {}
         for uid in uids:
-            if table.__contains__(uid):
+            if uid in table:
                 res[uid] = table.get(uid)
         return res
 
@@ -216,13 +216,13 @@ def upsert_entries(old_values: dict, new_values: dict):
         for uid, new_value in new_values.items():
             if old_values.get(uid) == entry_table.get(uid):
                 entry_table[uid] = new_value
-            elif entry_table.__contains__(uid):
+            elif uid in entry_table:
                 res[uid] = entry_table[uid]
         return res
 
     def insert_links(new_links: Dict):
         for uid, value in new_links.items():
-            if chain_table.__contains__(uid):
+            if uid in chain_table:
                 raise ValueError('collision in the Chain Table on uid: ' + uid)
             chain_table[uid] = value
 
@@ -342,7 +342,7 @@ def setUp(self) -> None:
                 redis_url,
             ),
             'rest': Findex.new_with_rest_backend(
-                self.findex_key, self.label, token.__str__(), url
+                self.findex_key, self.label, str(token), url
             ),
             'custom': Findex.new_with_custom_backend(
                 self.findex_key, self.label, entry_callbacks, chain_callbacks
@@ -452,7 +452,7 @@ def test_compact(self) -> None:
             def filter_obsolete_data(dataset: Set[Location]):
                 res = set()
                 for data in dataset:
-                    if self.db.__contains__(data.__int__()):
+                    if int(data) in self.db:
                         res.add(data)
                 return res