diff --git a/Cargo.toml b/Cargo.toml index 1f1a6fbe..ee3f5d88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "bindings/tbdex_uniffi", + "bindings/tbdex_uniffi", "crates/pfi_exemplar_integration_test", "crates/tbdex", ] default-members = [ @@ -17,4 +17,4 @@ license-file = "LICENSE" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" thiserror = "1.0.50" -web5 = { git = "https://github.com/TBD54566975/web5-rs", rev = "c6db872c0f7c3ab20970ca610e57481a0649f726" } +web5 = { git = "https://github.com/TBD54566975/web5-rs", rev = "f4de14fbada87543a335e1b8dd4442cd21c04db7" } diff --git a/bindings/tbdex_uniffi/Cargo.toml b/bindings/tbdex_uniffi/Cargo.toml index 1e1b9222..1a421c99 100644 --- a/bindings/tbdex_uniffi/Cargo.toml +++ b/bindings/tbdex_uniffi/Cargo.toml @@ -7,11 +7,12 @@ repository.workspace = true license-file.workspace = true [dependencies] +serde_json = { workspace = true } tbdex = { path = "../../crates/tbdex" } thiserror = { workspace = true } uniffi = { version = "0.27.1", features = ["cli"] } web5 = { workspace = true } -web5_uniffi_wrapper = { git = "https://github.com/TBD54566975/web5-rs", rev = "c6db872c0f7c3ab20970ca610e57481a0649f726" } +web5_uniffi_wrapper = { git = "https://github.com/TBD54566975/web5-rs", rev = "f4de14fbada87543a335e1b8dd4442cd21c04db7" } [build-dependencies] uniffi = { version = "0.27.1", features = ["build"] } diff --git a/bindings/tbdex_uniffi/src/errors.rs b/bindings/tbdex_uniffi/src/errors.rs index 7c39b1a8..3d63ce51 100644 --- a/bindings/tbdex_uniffi/src/errors.rs +++ b/bindings/tbdex_uniffi/src/errors.rs @@ -1,6 +1,7 @@ -use std::sync::Arc; +use serde_json::Error as SerdeJsonError; +use std::sync::{Arc, PoisonError}; use std::{any::type_name, fmt::Debug}; -use tbdex::http_client::TbdexHttpClientError; +use tbdex::http_client::HttpClientError; use tbdex::messages::MessageError; use tbdex::resources::ResourceError; use thiserror::Error; @@ -16,6 +17,13 @@ pub enum RustCoreError { } impl RustCoreError { + pub fn from_poison_error(error: PoisonError, error_type: &str) -> Arc { + Arc::new(RustCoreError::Error { + r#type: error_type.to_string(), + variant: "PoisonError".to_string(), + message: error.to_string(), + }) + } fn new(error: T) -> Self where T: std::error::Error + 'static, @@ -76,8 +84,14 @@ impl From for RustCoreError { } } -impl From for RustCoreError { - fn from(error: TbdexHttpClientError) -> Self { +impl From for RustCoreError { + fn from(error: HttpClientError) -> Self { + RustCoreError::new(error) + } +} + +impl From for RustCoreError { + fn from(error: SerdeJsonError) -> Self { RustCoreError::new(error) } } diff --git a/bindings/tbdex_uniffi/src/http_client/balances.rs b/bindings/tbdex_uniffi/src/http_client/balances.rs new file mode 100644 index 00000000..7ce53054 --- /dev/null +++ b/bindings/tbdex_uniffi/src/http_client/balances.rs @@ -0,0 +1,16 @@ +use crate::{errors::Result, resources::balance::Balance}; +use std::sync::{Arc, RwLock}; +use web5_uniffi_wrapper::dids::bearer_did::BearerDid; + +pub fn get_balances(pfi_did_uri: String, bearer_did: Arc) -> Result>> { + let inner_balances = + tbdex::http_client::balances::get_balances(&pfi_did_uri, &bearer_did.0.clone()) + .map_err(|e| Arc::new(e.into()))?; + + let balances = inner_balances + .into_iter() + .map(|b| Arc::new(Balance(Arc::new(RwLock::new(b))))) + .collect(); + + Ok(balances) +} diff --git a/bindings/tbdex_uniffi/src/http_client/exchanges.rs b/bindings/tbdex_uniffi/src/http_client/exchanges.rs new file mode 100644 index 00000000..d8bccc1b --- /dev/null +++ b/bindings/tbdex_uniffi/src/http_client/exchanges.rs @@ -0,0 +1,80 @@ +use crate::{ + errors::Result, + messages::{close::Close, order::Order, order_status::OrderStatus, quote::Quote, rfq::Rfq}, +}; +use std::sync::{Arc, RwLock}; +use tbdex::http_client::exchanges::Exchange as InnerExchange; +use web5_uniffi_wrapper::dids::bearer_did::BearerDid; + +pub struct Exchange { + pub rfq: Arc, + pub quote: Option>, + pub order: Option>, + pub order_statuses: Option>>, + pub close: Option>, +} + +impl Exchange { + pub fn from_inner(inner: InnerExchange) -> Self { + Self { + rfq: Arc::new(Rfq(Arc::new(RwLock::new(inner.rfq.clone())))), + quote: inner + .quote + .as_ref() + .map(|q| Arc::new(Quote(Arc::new(RwLock::new(q.clone()))))), + order: inner + .order + .as_ref() + .map(|o| Arc::new(Order(Arc::new(RwLock::new(o.clone()))))), + order_statuses: inner.order_statuses.as_ref().map(|os| { + os.iter() + .map(|o| Arc::new(OrderStatus(Arc::new(RwLock::new(o.clone()))))) + .collect::>() + }), + close: inner + .close + .as_ref() + .map(|c| Arc::new(Close(Arc::new(RwLock::new(c.clone()))))), + } + } +} + +pub fn create_exchange(rfq: Arc, reply_to: Option) -> Result<()> { + tbdex::http_client::exchanges::create_exchange(&rfq.to_inner()?, reply_to) + .map_err(|e| Arc::new(e.into()))?; + Ok(()) +} + +pub fn submit_order(order: Arc) -> Result<()> { + tbdex::http_client::exchanges::submit_order(&order.get_data()?) + .map_err(|e| Arc::new(e.into()))?; + Ok(()) +} + +pub fn submit_close(close: Arc) -> Result<()> { + tbdex::http_client::exchanges::submit_close(&close.get_data()?) + .map_err(|e| Arc::new(e.into()))?; + Ok(()) +} + +pub fn get_exchange( + pfi_did_uri: String, + bearer_did: Arc, + exchange_id: String, +) -> Result { + let inner_exchange = tbdex::http_client::exchanges::get_exchange( + &pfi_did_uri, + &bearer_did.0.clone(), + &exchange_id, + ) + .map_err(|e| Arc::new(e.into()))?; + + Ok(Exchange::from_inner(inner_exchange)) +} + +pub fn get_exchanges(pfi_did_uri: String, bearer_did: Arc) -> Result> { + let exchange_ids = + tbdex::http_client::exchanges::get_exchanges(&pfi_did_uri, &bearer_did.0.clone()) + .map_err(|e| Arc::new(e.into()))?; + Ok(exchange_ids) +} diff --git a/bindings/tbdex_uniffi/src/http_client/mod.rs b/bindings/tbdex_uniffi/src/http_client/mod.rs index e80e6637..37dbd39a 100644 --- a/bindings/tbdex_uniffi/src/http_client/mod.rs +++ b/bindings/tbdex_uniffi/src/http_client/mod.rs @@ -1,73 +1,3 @@ -use crate::{ - errors::Result, - messages::{close::Close, order::Order, rfq::Rfq, Message, OuterMessage}, - resources::{balance::Balance, offering::Offering}, -}; -use std::sync::Arc; -use tbdex::http_client::TbdexHttpClient; -use web5_uniffi_wrapper::dids::bearer_did::BearerDid; - -pub fn get_offerings(pfi_did: String) -> Result>> { - let inner_offerings = - TbdexHttpClient::get_offerings(pfi_did).map_err(|e| Arc::new(e.into()))?; - - let offerings = inner_offerings - .into_iter() - .map(|o| Arc::new(Offering(o))) - .collect(); - - Ok(offerings) -} - -pub fn get_balances(pfi_did: String, requestor_did: Arc) -> Result>> { - let inner_balances = TbdexHttpClient::get_balances(pfi_did, requestor_did.0.clone()) - .map_err(|e| Arc::new(e.into()))?; - - let balances = inner_balances - .into_iter() - .map(|o| Arc::new(Balance(o))) - .collect(); - - Ok(balances) -} - -pub fn create_exchange(rfq: Arc, reply_to: Option) -> Result<()> { - TbdexHttpClient::create_exchange(rfq.0.clone(), reply_to).map_err(|e| Arc::new(e.into()))?; - Ok(()) -} - -pub fn submit_order(order: Arc) -> Result<()> { - TbdexHttpClient::submit_order(order.0.clone()).map_err(|e| Arc::new(e.into()))?; - Ok(()) -} - -pub fn submit_close(close: Arc) -> Result<()> { - TbdexHttpClient::submit_close(close.0.clone()).map_err(|e| Arc::new(e.into()))?; - Ok(()) -} - -pub fn get_exchange( - pfi_did: String, - requestor_did: Arc, - exchange_id: String, -) -> Result>> { - let inner_messages = - TbdexHttpClient::get_exchange(pfi_did, requestor_did.0.clone(), exchange_id) - .map_err(|e| Arc::new(e.into()))?; - - let messages = inner_messages - .into_iter() - .map(|m| { - let outer_message: Arc = Arc::new(OuterMessage(m)); - outer_message - }) - .collect(); - - Ok(messages) -} - -pub fn get_exchanges(pfi_did: String, requestor_did: Arc) -> Result> { - let exchange_ids = TbdexHttpClient::get_exchanges(pfi_did, requestor_did.0.clone()) - .map_err(|e| Arc::new(e.into()))?; - Ok(exchange_ids) -} +pub mod balances; +pub mod exchanges; +pub mod offerings; diff --git a/bindings/tbdex_uniffi/src/http_client/offerings.rs b/bindings/tbdex_uniffi/src/http_client/offerings.rs new file mode 100644 index 00000000..838e1ffc --- /dev/null +++ b/bindings/tbdex_uniffi/src/http_client/offerings.rs @@ -0,0 +1,14 @@ +use crate::{errors::Result, resources::offering::Offering}; +use std::sync::{Arc, RwLock}; + +pub fn get_offerings(pfi_did_uri: String) -> Result>> { + let inner_offerings = tbdex::http_client::offerings::get_offerings(&pfi_did_uri) + .map_err(|e| Arc::new(e.into()))?; + + let offerings = inner_offerings + .into_iter() + .map(|o| Arc::new(Offering(Arc::new(RwLock::new(o))))) + .collect(); + + Ok(offerings) +} diff --git a/bindings/tbdex_uniffi/src/lib.rs b/bindings/tbdex_uniffi/src/lib.rs index 2240f960..268a0995 100644 --- a/bindings/tbdex_uniffi/src/lib.rs +++ b/bindings/tbdex_uniffi/src/lib.rs @@ -7,41 +7,56 @@ mod errors; use crate::{ errors::RustCoreError, http_client::{ - create_exchange, get_balances, get_exchange, get_exchanges, get_offerings, submit_close, - submit_order, + balances::get_balances, + exchanges::{ + create_exchange, get_exchange, get_exchanges, submit_close, submit_order, + Exchange as ExchangeData, + }, + offerings::get_offerings, }, messages::{ - close::Close, order::Order, order_status::OrderStatus, quote::Quote, rfq::Rfq, Message, + close::Close, + order::Order, + order_status::OrderStatus, + quote::Quote, + rfq::{ + data::{ + CreateRfqData as CreateRfqDataData, + CreateSelectedPayinMethod as CreateSelectedPayinMethodData, + CreateSelectedPayoutMethod as CreateSelectedPayoutMethodData, + PrivatePaymentDetails as PrivatePaymentDetailsData, Rfq as RfqData, + RfqData as RfqDataData, RfqPrivateData as RfqPrivateDataData, + SelectedPayinMethod as SelectedPayinMethodData, + SelectedPayoutMethod as SelectedPayoutMethodData, + }, + Rfq, + }, + }, + resources::{ + balance::Balance, + offering::{ + data::{ + Offering as OfferingData, OfferingData as OfferingDataData, + PayinDetails as PayinDetailsData, PayinMethod as PayinMethodData, + PayoutDetails as PayoutDetailsData, PayoutMethod as PayoutMethodData, + }, + Offering, + }, }, - resources::{balance::Balance, offering::Offering, Resource}, }; use tbdex::{ messages::{ close::{Close as CloseData, CloseData as CloseDataData}, - order::Order as OrderData, + order::{Order as OrderData, OrderData as OrderDataData}, order_status::{OrderStatus as OrderStatusData, OrderStatusData as OrderStatusDataData}, quote::{ PaymentInstructions as PaymentInstructionsData, Quote as QuoteData, QuoteData as QuoteDataData, QuoteDetails as QuoteDetailsData, }, - rfq::{ - CreateRfqData as CreateRfqDataData, - CreateSelectedPayinMethod as CreateSelectedPayinMethodData, - CreateSelectedPayoutMethod as CreateSelectedPayoutMethodData, - PrivatePaymentDetails as PrivatePaymentDetailsData, Rfq as RfqData, - RfqData as RfqDataData, RfqPrivateData as RfqPrivateDataData, - SelectedPayinMethod as SelectedPayinMethodData, - SelectedPayoutMethod as SelectedPayoutMethodData, - }, MessageKind, MessageMetadata as MessageMetadataData, }, resources::{ balance::{Balance as BalanceData, BalanceData as BalanceDataData}, - offering::{ - Offering as OfferingData, OfferingData as OfferingDataData, - PayinDetails as PayinDetailsData, PayinMethod as PayinMethodData, - PayoutDetails as PayoutDetailsData, PayoutMethod as PayoutMethodData, - }, ResourceKind, ResourceMetadata as ResourceMetadataData, }, }; @@ -62,14 +77,23 @@ use web5::apid::{ }; use web5_uniffi_wrapper::{ credentials::presentation_definition::PresentationDefinition, - crypto::key_manager::KeyManager, + crypto::{in_memory_key_manager::InMemoryKeyManager, key_manager::KeyManager}, dids::bearer_did::{BearerDid, BearerDidData}, dsa::Signer, errors::RustCoreError as Web5RustCoreError, }; -pub fn hello_world() { - println!("Hello world") +// 🚧 TODO temporary hack in place while did:dht resolution is incomplete +pub fn tmp_hack_bearer_did( + did: DidData, + document: DocumentData, + key_manager: std::sync::Arc, +) -> std::sync::Arc { + std::sync::Arc::new(BearerDid(web5::apid::dids::bearer_did::BearerDid { + did, + document, + key_manager: key_manager.to_inner(), + })) } uniffi::include_scaffolding!("tbdex"); diff --git a/bindings/tbdex_uniffi/src/messages/close.rs b/bindings/tbdex_uniffi/src/messages/close.rs index d95dd92c..301099da 100644 --- a/bindings/tbdex_uniffi/src/messages/close.rs +++ b/bindings/tbdex_uniffi/src/messages/close.rs @@ -1,46 +1,51 @@ -use super::Message; -use crate::errors::Result; -use std::sync::Arc; -use tbdex::messages::{ - close::{Close as InnerClose, CloseData}, - Message as InnerMessage, -}; +use crate::errors::{Result, RustCoreError}; +use std::sync::{Arc, RwLock}; +use tbdex::messages::close::{Close as InnerClose, CloseData}; use web5_uniffi_wrapper::dids::bearer_did::BearerDid; -pub struct Close(pub InnerClose); +pub struct Close(pub Arc>); impl Close { pub fn new( + bearer_did: Arc, to: String, from: String, exchange_id: String, data: CloseData, protocol: String, external_id: Option, - ) -> Self { - Self(InnerClose::new( - to, - from, - exchange_id, - data, - protocol, + ) -> Result { + let close = InnerClose::new( + &bearer_did.0.clone(), + &to, + &from, + &exchange_id, + &data, + &protocol, external_id, - )) + ) + .map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(close)))) } - pub fn get_data(&self) -> InnerClose { - self.0.clone() + pub fn from_json_string(json: &str) -> Result { + let inner_close = InnerClose::from_json_string(json).map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_close)))) } -} -impl Message for Close { - fn sign(&self, bearer_did: Arc) -> Result<()> { - self.0 - .sign(bearer_did.0.clone()) - .map_err(|e| Arc::new(e.into())) + pub fn to_json(&self) -> Result { + let inner_close = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + inner_close.to_json().map_err(|e| Arc::new(e.into())) } - fn verify(&self) -> Result<()> { - self.0.verify().map_err(|e| Arc::new(e.into())) + pub fn get_data(&self) -> Result { + let close = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(close.clone()) } } diff --git a/bindings/tbdex_uniffi/src/messages/mod.rs b/bindings/tbdex_uniffi/src/messages/mod.rs index 7e1ae2f7..47327bac 100644 --- a/bindings/tbdex_uniffi/src/messages/mod.rs +++ b/bindings/tbdex_uniffi/src/messages/mod.rs @@ -3,27 +3,3 @@ pub mod order; pub mod order_status; pub mod quote; pub mod rfq; - -use crate::errors::Result; -use std::sync::Arc; -use tbdex::messages::Message as InnerMessage; -use web5_uniffi_wrapper::dids::bearer_did::BearerDid; - -pub trait Message: Send + Sync { - fn sign(&self, bearer_did: Arc) -> Result<()>; - fn verify(&self) -> Result<()>; -} - -pub struct OuterMessage(pub Arc); - -impl Message for OuterMessage { - fn sign(&self, bearer_did: Arc) -> Result<()> { - self.0 - .sign(bearer_did.0.clone()) - .map_err(|e| Arc::new(e.into())) - } - - fn verify(&self) -> Result<()> { - self.0.verify().map_err(|e| Arc::new(e.into())) - } -} diff --git a/bindings/tbdex_uniffi/src/messages/order.rs b/bindings/tbdex_uniffi/src/messages/order.rs index 06318166..54559269 100644 --- a/bindings/tbdex_uniffi/src/messages/order.rs +++ b/bindings/tbdex_uniffi/src/messages/order.rs @@ -1,41 +1,49 @@ -use super::Message; -use crate::errors::Result; -use std::sync::Arc; -use tbdex::messages::{order::Order as InnerOrder, Message as InnerMessage}; +use crate::errors::{Result, RustCoreError}; +use std::sync::{Arc, RwLock}; +use tbdex::messages::order::Order as InnerOrder; use web5_uniffi_wrapper::dids::bearer_did::BearerDid; -pub struct Order(pub InnerOrder); +pub struct Order(pub Arc>); impl Order { pub fn new( + bearer_did: Arc, to: String, from: String, exchange_id: String, protocol: String, external_id: Option, - ) -> Self { - Self(InnerOrder::new( - to, - from, - exchange_id, - protocol, + ) -> Result { + let order = InnerOrder::new( + &bearer_did.0.clone(), + &to, + &from, + &exchange_id, + &protocol, external_id, - )) + ) + .map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(order)))) } - pub fn get_data(&self) -> InnerOrder { - self.0.clone() + pub fn from_json_string(json: &str) -> Result { + let inner_order = InnerOrder::from_json_string(json).map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_order)))) } -} -impl Message for Order { - fn sign(&self, bearer_did: Arc) -> Result<()> { - self.0 - .sign(bearer_did.0.clone()) - .map_err(|e| Arc::new(e.into())) + pub fn to_json(&self) -> Result { + let inner_order = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + inner_order.to_json().map_err(|e| Arc::new(e.into())) } - fn verify(&self) -> Result<()> { - self.0.verify().map_err(|e| Arc::new(e.into())) + pub fn get_data(&self) -> Result { + let order = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(order.clone()) } } diff --git a/bindings/tbdex_uniffi/src/messages/order_status.rs b/bindings/tbdex_uniffi/src/messages/order_status.rs index 77004e80..213d48af 100644 --- a/bindings/tbdex_uniffi/src/messages/order_status.rs +++ b/bindings/tbdex_uniffi/src/messages/order_status.rs @@ -1,46 +1,52 @@ -use super::Message; -use crate::errors::Result; -use std::sync::Arc; -use tbdex::messages::{ - order_status::{OrderStatus as InnerOrderStatus, OrderStatusData}, - Message as InnerMessage, -}; +use crate::errors::{Result, RustCoreError}; +use std::sync::{Arc, RwLock}; +use tbdex::messages::order_status::{OrderStatus as InnerOrderStatus, OrderStatusData}; use web5_uniffi_wrapper::dids::bearer_did::BearerDid; -pub struct OrderStatus(pub InnerOrderStatus); +pub struct OrderStatus(pub Arc>); impl OrderStatus { pub fn new( + bearer_did: Arc, to: String, from: String, exchange_id: String, data: OrderStatusData, protocol: String, external_id: Option, - ) -> Self { - Self(InnerOrderStatus::new( - to, - from, - exchange_id, - data, - protocol, + ) -> Result { + let order_status = InnerOrderStatus::new( + &bearer_did.0.clone(), + &to, + &from, + &exchange_id, + &data, + &protocol, external_id, - )) + ) + .map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(order_status)))) } - pub fn get_data(&self) -> InnerOrderStatus { - self.0.clone() + pub fn from_json_string(json: &str) -> Result { + let inner_order_status = + InnerOrderStatus::from_json_string(json).map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_order_status)))) } -} -impl Message for OrderStatus { - fn sign(&self, bearer_did: Arc) -> Result<()> { - self.0 - .sign(bearer_did.0.clone()) - .map_err(|e| Arc::new(e.into())) + pub fn to_json(&self) -> Result { + let inner_order_status = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + inner_order_status.to_json().map_err(|e| Arc::new(e.into())) } - fn verify(&self) -> Result<()> { - self.0.verify().map_err(|e| Arc::new(e.into())) + pub fn get_data(&self) -> Result { + let order_status = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(order_status.clone()) } } diff --git a/bindings/tbdex_uniffi/src/messages/quote.rs b/bindings/tbdex_uniffi/src/messages/quote.rs index 8d4d8736..e0d14b13 100644 --- a/bindings/tbdex_uniffi/src/messages/quote.rs +++ b/bindings/tbdex_uniffi/src/messages/quote.rs @@ -1,46 +1,51 @@ -use super::Message; -use crate::errors::Result; -use std::sync::Arc; -use tbdex::messages::{ - quote::{Quote as InnerQuote, QuoteData}, - Message as InnerMessage, -}; +use crate::errors::{Result, RustCoreError}; +use std::sync::{Arc, RwLock}; +use tbdex::messages::quote::{Quote as InnerQuote, QuoteData}; use web5_uniffi_wrapper::dids::bearer_did::BearerDid; -pub struct Quote(pub InnerQuote); +pub struct Quote(pub Arc>); impl Quote { pub fn new( + bearer_did: Arc, to: String, from: String, exchange_id: String, data: QuoteData, protocol: String, external_id: Option, - ) -> Self { - Self(InnerQuote::new( - to, - from, - exchange_id, - data, - protocol, + ) -> Result { + let quote = InnerQuote::new( + &bearer_did.0.clone(), + &to, + &from, + &exchange_id, + &data, + &protocol, external_id, - )) + ) + .map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(quote)))) } - pub fn get_data(&self) -> InnerQuote { - self.0.clone() + pub fn from_json_string(json: &str) -> Result { + let inner_quote = InnerQuote::from_json_string(json).map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_quote)))) } -} -impl Message for Quote { - fn sign(&self, bearer_did: Arc) -> Result<()> { - self.0 - .sign(bearer_did.0.clone()) - .map_err(|e| Arc::new(e.into())) + pub fn to_json(&self) -> Result { + let inner_quote = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + inner_quote.to_json().map_err(|e| Arc::new(e.into())) } - fn verify(&self) -> Result<()> { - self.0.verify().map_err(|e| Arc::new(e.into())) + pub fn get_data(&self) -> Result { + let quote = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(quote.clone()) } } diff --git a/bindings/tbdex_uniffi/src/messages/rfq.rs b/bindings/tbdex_uniffi/src/messages/rfq.rs index 437070ad..c3815237 100644 --- a/bindings/tbdex_uniffi/src/messages/rfq.rs +++ b/bindings/tbdex_uniffi/src/messages/rfq.rs @@ -1,62 +1,270 @@ -use super::Message; -use crate::{errors::Result, resources::offering::Offering}; -use std::sync::Arc; -use tbdex::messages::{ - rfq::{CreateRfqData, Rfq as InnerRfq}, - Message as InnerMessage, +use crate::{ + errors::{Result, RustCoreError}, + resources::offering::Offering, }; +use std::sync::{Arc, RwLock}; +use tbdex::messages::rfq::Rfq as InnerRfq; use web5_uniffi_wrapper::dids::bearer_did::BearerDid; -pub struct Rfq(pub InnerRfq); +pub struct Rfq(pub Arc>); impl Rfq { pub fn new( + bearer_did: Arc, to: String, from: String, - create_rfq_data: CreateRfqData, + create_rfq_data: data::CreateRfqData, protocol: String, external_id: Option, - ) -> Self { - Self(InnerRfq::new( - to, - from, - create_rfq_data, - protocol, + ) -> Result { + let rfq = InnerRfq::new( + &bearer_did.0.clone(), + &to, + &from, + &create_rfq_data.to_inner()?, + &protocol, external_id, - )) + ) + .map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(rfq)))) } - pub fn get_data(&self) -> InnerRfq { - self.0.clone() + pub fn from_json_string(json: &str) -> Result { + let inner_rfq = InnerRfq::from_json_string(json).map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_rfq)))) + } + + pub fn to_json(&self) -> Result { + let inner_rfq = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + inner_rfq.to_json().map_err(|e| Arc::new(e.into())) + } + + pub fn get_data(&self) -> Result { + let rfq = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(data::Rfq { + metadata: rfq.metadata.clone(), + data: data::RfqData::from_inner(rfq.data.clone())?, + private_data: data::RfqPrivateData::from_inner(rfq.private_data.clone())?, + signature: rfq.signature.clone(), + }) + } + + pub fn to_inner(&self) -> Result { + let inner_rfq = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(inner_rfq.clone()) } pub fn verify_offering_requirements(&self, offering: Arc) -> Result { - self.0 - .verify_offering_requirements(offering.0.clone()) + let rfq = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + rfq.verify_offering_requirements(&offering.to_inner()?) .map_err(|e| Arc::new(e.into())) } pub fn verify_all_private_data(&self) -> Result { - self.0 - .verify_all_private_data() + let rfq = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + rfq.verify_all_private_data() .map_err(|e| Arc::new(e.into())) } pub fn verify_present_private_data(&self) -> Result { - self.0 - .verify_present_private_data() + let rfq = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + rfq.verify_present_private_data() .map_err(|e| Arc::new(e.into())) } } -impl Message for Rfq { - fn sign(&self, bearer_did: Arc) -> Result<()> { - self.0 - .sign(bearer_did.0.clone()) - .map_err(|e| Arc::new(e.into())) +pub mod data { + use super::*; + use tbdex::messages::{ + rfq::{ + CreateRfqData as InnerCreateRfqData, + CreateSelectedPayinMethod as InnerCreateSelectedPayinMethod, + CreateSelectedPayoutMethod as InnerCreateSelectedPayoutMethod, + PrivatePaymentDetails as InnerPrivatePaymentDetails, RfqData as InnerRfqData, + RfqPrivateData as InnerRfqPrivateData, SelectedPayinMethod as InnerSelectedPayinMethod, + SelectedPayoutMethod as InnerSelectedPayoutMethod, + }, + MessageMetadata, + }; + + #[derive(Clone)] + pub struct CreateRfqData { + pub offering_id: String, + pub payin: CreateSelectedPayinMethod, + pub payout: CreateSelectedPayoutMethod, + pub claims: Vec, + } + + impl CreateRfqData { + pub fn to_inner(&self) -> Result { + Ok(InnerCreateRfqData { + offering_id: self.offering_id.clone(), + payin: self.payin.to_inner()?, + payout: self.payout.to_inner()?, + claims: self.claims.clone(), + }) + } + } + + #[derive(Clone)] + pub struct CreateSelectedPayinMethod { + pub kind: String, + pub payment_details: Option, // JSON serialized + pub amount: String, + } + + impl CreateSelectedPayinMethod { + pub fn to_inner(&self) -> Result { + let payment_details = match &self.payment_details { + Some(pd) => Some( + serde_json::from_str::(pd) + .map_err(|e| Arc::new(e.into()))?, + ), + None => None, + }; + Ok(InnerCreateSelectedPayinMethod { + kind: self.kind.clone(), + payment_details, + amount: self.amount.clone(), + }) + } + } + + #[derive(Clone)] + pub struct CreateSelectedPayoutMethod { + pub kind: String, + pub payment_details: Option, // JSON serialized + } + + impl CreateSelectedPayoutMethod { + pub fn to_inner(&self) -> Result { + let payment_details = match &self.payment_details { + Some(pd) => Some( + serde_json::from_str::(pd) + .map_err(|e| Arc::new(e.into()))?, + ), + None => None, + }; + Ok(InnerCreateSelectedPayoutMethod { + kind: self.kind.clone(), + payment_details, + }) + } + } + + pub struct Rfq { + pub metadata: MessageMetadata, + pub data: RfqData, + pub private_data: RfqPrivateData, + pub signature: String, + } + + #[derive(Clone)] + pub struct RfqData { + pub offering_id: String, + pub payin: SelectedPayinMethod, + pub payout: SelectedPayoutMethod, + pub claims_hash: Option, + } + + impl RfqData { + pub fn from_inner(inner: InnerRfqData) -> Result { + Ok(Self { + offering_id: inner.offering_id, + payin: SelectedPayinMethod::from_inner(inner.payin)?, + payout: SelectedPayoutMethod::from_inner(inner.payout)?, + claims_hash: inner.claims_hash, + }) + } + } + + #[derive(Clone)] + pub struct SelectedPayinMethod { + pub kind: String, + pub payment_details_hash: Option, + pub amount: String, + } + + impl SelectedPayinMethod { + pub fn from_inner(inner: InnerSelectedPayinMethod) -> Result { + Ok(Self { + kind: inner.kind, + payment_details_hash: inner.payment_details_hash, + amount: inner.amount, + }) + } + } + + #[derive(Clone)] + pub struct SelectedPayoutMethod { + pub kind: String, + pub payment_details_hash: Option, + } + + impl SelectedPayoutMethod { + pub fn from_inner(inner: InnerSelectedPayoutMethod) -> Result { + Ok(Self { + kind: inner.kind, + payment_details_hash: inner.payment_details_hash, + }) + } + } + + #[derive(Clone)] + pub struct RfqPrivateData { + pub salt: String, + pub payin: Option, + pub payout: Option, + pub claims: Option>, + } + + impl RfqPrivateData { + pub fn from_inner(inner: InnerRfqPrivateData) -> Result { + Ok(Self { + salt: inner.salt, + payin: inner + .payin + .map(PrivatePaymentDetails::from_inner) + .transpose()?, + payout: inner + .payout + .map(PrivatePaymentDetails::from_inner) + .transpose()?, + claims: inner.claims, + }) + } + } + + #[derive(Clone)] + pub struct PrivatePaymentDetails { + pub payment_details: Option, // JSON serialized } - fn verify(&self) -> Result<()> { - self.0.verify().map_err(|e| Arc::new(e.into())) + impl PrivatePaymentDetails { + pub fn from_inner(inner: InnerPrivatePaymentDetails) -> Result { + let payment_details = match &inner.payment_details { + Some(pd) => Some(serde_json::to_string(pd).map_err(|e| Arc::new(e.into()))?), + None => None, + }; + Ok(Self { payment_details }) + } } } diff --git a/bindings/tbdex_uniffi/src/resources/balance.rs b/bindings/tbdex_uniffi/src/resources/balance.rs index 056cdca1..87ca4926 100644 --- a/bindings/tbdex_uniffi/src/resources/balance.rs +++ b/bindings/tbdex_uniffi/src/resources/balance.rs @@ -1,32 +1,40 @@ -use super::Resource; -use crate::errors::Result; -use std::sync::Arc; -use tbdex::resources::{ - balance::{Balance as InnerBalance, BalanceData}, - Resource as InnerResource, -}; +use crate::errors::{Result, RustCoreError}; +use std::sync::{Arc, RwLock}; +use tbdex::resources::balance::{Balance as InnerBalance, BalanceData}; use web5_uniffi_wrapper::dids::bearer_did::BearerDid; -pub struct Balance(pub InnerBalance); +pub struct Balance(pub Arc>); impl Balance { - pub fn new(from: String, data: BalanceData, protocol: String) -> Self { - Self(InnerBalance::new(from, data, protocol)) + pub fn new( + bearer_did: Arc, + from: String, + data: BalanceData, + protocol: String, + ) -> Result { + let inner_balance = InnerBalance::new(&bearer_did.0.clone(), &from, &data, &protocol) + .map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_balance)))) } - pub fn get_data(&self) -> InnerBalance { - self.0.clone() + pub fn from_json_string(json: &str) -> Result { + let inner_balance = InnerBalance::from_json_string(json).map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_balance)))) } -} -impl Resource for Balance { - fn sign(&self, bearer_did: Arc) -> Result<()> { - self.0 - .sign(bearer_did.0.clone()) - .map_err(|e| Arc::new(e.into())) + pub fn to_json(&self) -> Result { + let inner_balance = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + inner_balance.to_json().map_err(|e| Arc::new(e.into())) } - fn verify(&self) -> Result<()> { - self.0.verify().map_err(|e| Arc::new(e.into())) + pub fn get_data(&self) -> Result { + let balance = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(balance.clone()) } } diff --git a/bindings/tbdex_uniffi/src/resources/mod.rs b/bindings/tbdex_uniffi/src/resources/mod.rs index 0c6f1a87..6c346747 100644 --- a/bindings/tbdex_uniffi/src/resources/mod.rs +++ b/bindings/tbdex_uniffi/src/resources/mod.rs @@ -1,11 +1,2 @@ pub mod balance; pub mod offering; - -use crate::errors::Result; -use std::sync::Arc; -use web5_uniffi_wrapper::dids::bearer_did::BearerDid; - -pub trait Resource: Send + Sync { - fn sign(&self, bearer_did: Arc) -> Result<()>; - fn verify(&self) -> Result<()>; -} diff --git a/bindings/tbdex_uniffi/src/resources/offering.rs b/bindings/tbdex_uniffi/src/resources/offering.rs index f634d161..24ab200d 100644 --- a/bindings/tbdex_uniffi/src/resources/offering.rs +++ b/bindings/tbdex_uniffi/src/resources/offering.rs @@ -1,32 +1,298 @@ -use super::Resource; -use crate::errors::Result; -use std::sync::Arc; -use tbdex::resources::{ - offering::{Offering as InnerOffering, OfferingData}, - Resource as InnerResource, -}; +use crate::errors::{Result, RustCoreError}; +use std::sync::{Arc, RwLock}; +use tbdex::resources::offering::Offering as InnerOffering; use web5_uniffi_wrapper::dids::bearer_did::BearerDid; -pub struct Offering(pub InnerOffering); +pub struct Offering(pub Arc>); impl Offering { - pub fn new(from: String, data: OfferingData, protocol: String) -> Self { - Self(InnerOffering::new(from, data, protocol)) + pub fn new( + bearer_did: Arc, + from: String, + data: data::OfferingData, + protocol: String, + ) -> Result { + let inner_offering = + InnerOffering::new(&bearer_did.0.clone(), &from, &data.to_inner()?, &protocol) + .map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_offering)))) } - pub fn get_data(&self) -> InnerOffering { - self.0.clone() + pub fn from_json_string(json: &str) -> Result { + let inner_offering = + InnerOffering::from_json_string(json).map_err(|e| Arc::new(e.into()))?; + Ok(Self(Arc::new(RwLock::new(inner_offering)))) + } + + pub fn to_json(&self) -> Result { + let inner_offering = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + inner_offering.to_json().map_err(|e| Arc::new(e.into())) + } + + pub fn get_data(&self) -> Result { + let inner_offering = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(data::Offering { + metadata: inner_offering.metadata.clone(), + data: data::OfferingData::from_inner(inner_offering.data.clone())?, + signature: inner_offering.signature.clone(), + }) + } + + pub fn to_inner(&self) -> Result { + let inner_offering = self + .0 + .read() + .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; + Ok(inner_offering.clone()) } } -impl Resource for Offering { - fn sign(&self, bearer_did: Arc) -> Result<()> { - self.0 - .sign(bearer_did.0.clone()) - .map_err(|e| Arc::new(e.into())) +pub mod data { + use super::*; + use tbdex::resources::{ + offering::{ + OfferingData as InnerOfferingData, PayinDetails as InnerPayinDetails, + PayinMethod as InnerPayinMethod, PayoutDetails as InnerPayoutDetails, + PayoutMethod as InnerPayoutMethod, + }, + ResourceMetadata, + }; + use web5::apid::credentials::presentation_definition::PresentationDefinition; + + #[derive(Clone)] + pub struct Offering { + pub metadata: ResourceMetadata, + pub data: OfferingData, + pub signature: String, + } + + #[derive(Clone)] + pub struct OfferingData { + pub description: String, + pub payout_units_per_payin_unit: String, + pub payin: PayinDetails, + pub payout: PayoutDetails, + pub required_claims: Option, + } + + impl OfferingData { + pub fn to_inner(&self) -> Result { + Ok(InnerOfferingData { + description: self.description.clone(), + payout_units_per_payin_unit: self.payout_units_per_payin_unit.clone(), + payin: self.payin.to_inner()?, + payout: self.payout.to_inner()?, + required_claims: self.required_claims.clone(), + }) + } + + pub fn from_inner(inner: InnerOfferingData) -> Result { + Ok(Self { + description: inner.description.clone(), + payout_units_per_payin_unit: inner.payout_units_per_payin_unit.clone(), + payin: PayinDetails::from_inner(inner.payin.clone())?, + payout: PayoutDetails::from_inner(inner.payout.clone())?, + required_claims: inner.required_claims.clone(), + }) + } + } + + #[derive(Clone)] + pub struct PayinDetails { + pub currency_code: String, + pub min: Option, + pub max: Option, + pub methods: Vec, + } + + impl PayinDetails { + pub fn to_inner(&self) -> Result { + let methods: Result> = self + .methods + .clone() + .into_iter() + .map(|m| m.to_inner()) + .collect(); + + Ok(InnerPayinDetails { + currency_code: self.currency_code.clone(), + min: self.min.clone(), + max: self.max.clone(), + methods: methods?, + }) + } + + pub fn from_inner(inner: InnerPayinDetails) -> Result { + let methods: Result> = inner + .methods + .clone() + .into_iter() + .map(PayinMethod::from_inner) + .collect(); + + Ok(Self { + currency_code: inner.currency_code.clone(), + min: inner.min.clone(), + max: inner.max.clone(), + methods: methods?, + }) + } } - fn verify(&self) -> Result<()> { - self.0.verify().map_err(|e| Arc::new(e.into())) + #[derive(Clone)] + pub struct PayinMethod { + pub kind: String, + pub name: Option, + pub description: Option, + pub group: Option, + pub required_payment_details: Option, // JSON serialized + pub fee: Option, + pub min: Option, + pub max: Option, + } + + impl PayinMethod { + pub fn to_inner(&self) -> Result { + let required_payment_details = match self.required_payment_details.clone() { + None => None, + Some(s) => Some( + serde_json::from_str::(&s) + .map_err(|e| Arc::new(e.into()))?, + ), + }; + + Ok(InnerPayinMethod { + kind: self.kind.clone(), + name: self.name.clone(), + description: self.description.clone(), + group: self.group.clone(), + required_payment_details, + fee: self.fee.clone(), + min: self.min.clone(), + max: self.max.clone(), + }) + } + + pub fn from_inner(inner: InnerPayinMethod) -> Result { + let required_payment_details = match inner.required_payment_details.clone() { + None => None, + Some(s) => Some(serde_json::to_string(&s).map_err(|e| Arc::new(e.into()))?), + }; + + Ok(Self { + kind: inner.kind.clone(), + name: inner.name.clone(), + description: inner.description.clone(), + group: inner.group.clone(), + required_payment_details, + fee: inner.fee.clone(), + min: inner.min.clone(), + max: inner.max.clone(), + }) + } + } + + #[derive(Clone)] + pub struct PayoutDetails { + pub currency_code: String, + pub min: Option, + pub max: Option, + pub methods: Vec, + } + + impl PayoutDetails { + pub fn to_inner(&self) -> Result { + let methods: Result> = self + .methods + .clone() + .into_iter() + .map(|m| m.to_inner()) + .collect(); + + Ok(InnerPayoutDetails { + currency_code: self.currency_code.clone(), + min: self.min.clone(), + max: self.max.clone(), + methods: methods?, + }) + } + + pub fn from_inner(inner: InnerPayoutDetails) -> Result { + let methods: Result> = inner + .methods + .clone() + .into_iter() + .map(PayoutMethod::from_inner) + .collect(); + + Ok(Self { + currency_code: inner.currency_code.clone(), + min: inner.min.clone(), + max: inner.max.clone(), + methods: methods?, + }) + } + } + + #[derive(Clone)] + pub struct PayoutMethod { + pub kind: String, + pub name: Option, + pub description: Option, + pub group: Option, + pub required_payment_details: Option, // JSON serialized + pub fee: Option, + pub min: Option, + pub max: Option, + pub estimated_settlement_time: i64, + } + + impl PayoutMethod { + pub fn to_inner(&self) -> Result { + let required_payment_details = match self.required_payment_details.clone() { + None => None, + Some(s) => Some( + serde_json::from_str::(&s) + .map_err(|e| Arc::new(e.into()))?, + ), + }; + + Ok(InnerPayoutMethod { + kind: self.kind.clone(), + name: self.name.clone(), + description: self.description.clone(), + group: self.group.clone(), + required_payment_details, + fee: self.fee.clone(), + min: self.min.clone(), + max: self.max.clone(), + estimated_settlement_time: self.estimated_settlement_time, + }) + } + + pub fn from_inner(inner: InnerPayoutMethod) -> Result { + let required_payment_details = match inner.required_payment_details.clone() { + None => None, + Some(s) => Some(serde_json::to_string(&s).map_err(|e| Arc::new(e.into()))?), + }; + + Ok(Self { + kind: inner.kind.clone(), + name: inner.name.clone(), + description: inner.description.clone(), + group: inner.group.clone(), + required_payment_details, + fee: inner.fee.clone(), + min: inner.min.clone(), + max: inner.max.clone(), + estimated_settlement_time: inner.estimated_settlement_time, + }) + } } } diff --git a/bindings/tbdex_uniffi/src/tbdex.udl b/bindings/tbdex_uniffi/src/tbdex.udl index eea1df7f..ccb7cc18 100644 --- a/bindings/tbdex_uniffi/src/tbdex.udl +++ b/bindings/tbdex_uniffi/src/tbdex.udl @@ -1,10 +1,10 @@ namespace tbdex { - void hello_world(); + BearerDid tmp_hack_bearer_did(DidData did, DocumentData document, KeyManager key_manager); [Throws=RustCoreError] - sequence get_offerings(string pfi_did); + sequence get_offerings(string pfi_did_uri); [Throws=RustCoreError] - sequence get_balances(string pfi_did, BearerDid requestor_did); + sequence get_balances(string pfi_did_uri, BearerDid bearer_did); [Throws=RustCoreError] void create_exchange(Rfq rfq, string? reply_to); [Throws=RustCoreError] @@ -12,9 +12,9 @@ namespace tbdex { [Throws=RustCoreError] void submit_close(Close close); [Throws=RustCoreError] - sequence get_exchange(string pfi_did, BearerDid requestor_did, string exchange_id); + ExchangeData get_exchange(string pfi_did_uri, BearerDid bearer_did, string exchange_id); [Throws=RustCoreError] - sequence get_exchanges(string pfi_did, BearerDid requestor_did); + sequence get_exchanges(string pfi_did_uri, BearerDid bearer_did); }; interface RustCoreError { @@ -28,18 +28,9 @@ enum ResourceKind { "Balance" }; -[Trait] -interface Resource { - [Throws=RustCoreError] - void sign(BearerDid bearer_did); - [Throws=RustCoreError] - void verify(); -}; - dictionary ResourceMetadataData { ResourceKind kind; string from; - string to; string id; string protocol; string created_at; @@ -47,12 +38,14 @@ dictionary ResourceMetadataData { }; interface Offering { - constructor(string from, OfferingDataData data, string protocol); - OfferingData get_data(); [Throws=RustCoreError] - void sign(BearerDid bearer_did); + constructor(BearerDid bearer_did, string from, OfferingDataData data, string protocol); + [Name=from_json_string, Throws=RustCoreError] + constructor([ByRef] string json); + [Throws=RustCoreError] + string to_json(); [Throws=RustCoreError] - void verify(); + OfferingData get_data(); }; dictionary OfferingData { @@ -66,7 +59,7 @@ dictionary OfferingDataData { string payout_units_per_payin_unit; PayinDetailsData payin; PayoutDetailsData payout; - PresentationDefinitionData required_claims; + PresentationDefinitionData? required_claims; }; dictionary PayinDetailsData { @@ -118,12 +111,14 @@ dictionary BalanceDataData { }; interface Balance { - constructor(string from, BalanceDataData data, string protocol); - BalanceData get_data(); [Throws=RustCoreError] - void sign(BearerDid bearer_did); + constructor(BearerDid bearer_did, string from, BalanceDataData data, string protocol); + [Name=from_json_string, Throws=RustCoreError] + constructor([ByRef] string json); + [Throws=RustCoreError] + string to_json(); [Throws=RustCoreError] - void verify(); + BalanceData get_data(); }; enum MessageKind { @@ -134,14 +129,6 @@ enum MessageKind { "Close" }; -[Trait] -interface Message { - [Throws=RustCoreError] - void sign(BearerDid bearer_did); - [Throws=RustCoreError] - void verify(); -}; - dictionary MessageMetadataData { string from; string to; @@ -154,12 +141,14 @@ dictionary MessageMetadataData { }; interface Rfq { - constructor(string to, string from, CreateRfqDataData create_rfq_data, string protocol, string? external_id); - RfqData get_data(); [Throws=RustCoreError] - void sign(BearerDid bearer_did); + constructor(BearerDid bearer_did, string to, string from, CreateRfqDataData create_rfq_data, string protocol, string? external_id); + [Name=from_json_string, Throws=RustCoreError] + constructor([ByRef] string json); + [Throws=RustCoreError] + string to_json(); [Throws=RustCoreError] - void verify(); + RfqData get_data(); [Throws=RustCoreError] boolean verify_offering_requirements(Offering offering); @@ -185,13 +174,13 @@ dictionary CreateRfqDataData { dictionary CreateSelectedPayinMethodData { string kind; - string payment_details; + string? payment_details; string amount; }; dictionary CreateSelectedPayoutMethodData { string kind; - string payment_details; + string? payment_details; }; dictionary RfqDataData { @@ -220,16 +209,18 @@ dictionary RfqPrivateDataData { }; dictionary PrivatePaymentDetailsData { - string payment_details; + string? payment_details; }; interface Quote { - constructor(string to, string from, string exchange_id, QuoteDataData data, string protocol, string? external_id); - QuoteData get_data(); [Throws=RustCoreError] - void sign(BearerDid bearer_did); + constructor(BearerDid bearer_did, string to, string from, string exchange_id, QuoteDataData data, string protocol, string? external_id); + [Name=from_json_string, Throws=RustCoreError] + constructor([ByRef] string json); [Throws=RustCoreError] - void verify(); + string to_json(); + [Throws=RustCoreError] + QuoteData get_data(); }; dictionary QuoteData { @@ -257,26 +248,35 @@ dictionary PaymentInstructionsData { }; interface Order { - constructor(string to, string from, string exchange_id, string protocol, string? external_id); - OrderData get_data(); [Throws=RustCoreError] - void sign(BearerDid bearer_did); + constructor(BearerDid bearer_did, string to, string from, string exchange_id, string protocol, string? external_id); + [Name=from_json_string, Throws=RustCoreError] + constructor([ByRef] string json); [Throws=RustCoreError] - void verify(); + string to_json(); + [Throws=RustCoreError] + OrderData get_data(); }; dictionary OrderData { MessageMetadataData metadata; + OrderDataData data; string signature; }; +dictionary OrderDataData { + +}; + interface OrderStatus { - constructor(string to, string from, string exchange_id, OrderStatusDataData data, string protocol, string? external_id); - OrderStatusData get_data(); [Throws=RustCoreError] - void sign(BearerDid bearer_did); + constructor(BearerDid bearer_did, string to, string from, string exchange_id, OrderStatusDataData data, string protocol, string? external_id); + [Name=from_json_string, Throws=RustCoreError] + constructor([ByRef] string json); + [Throws=RustCoreError] + string to_json(); [Throws=RustCoreError] - void verify(); + OrderStatusData get_data(); }; dictionary OrderStatusData { @@ -290,12 +290,14 @@ dictionary OrderStatusDataData { }; interface Close { - constructor(string to, string from, string exchange_id, CloseDataData data, string protocol, string? external_id); - CloseData get_data(); [Throws=RustCoreError] - void sign(BearerDid bearer_did); + constructor(BearerDid bearer_did, string to, string from, string exchange_id, CloseDataData data, string protocol, string? external_id); + [Name=from_json_string, Throws=RustCoreError] + constructor([ByRef] string json); + [Throws=RustCoreError] + string to_json(); [Throws=RustCoreError] - void verify(); + CloseData get_data(); }; dictionary CloseData { @@ -309,6 +311,13 @@ dictionary CloseDataData { boolean? success; }; +dictionary ExchangeData { + Rfq rfq; + Quote? quote; + Order? order; + sequence? order_statuses; + Close? close; +}; @@ -389,6 +398,15 @@ interface KeyManager { Signer get_signer(JwkData public_jwk); }; +interface InMemoryKeyManager { + constructor(); + [Throws=Web5RustCoreError] + Signer get_signer(JwkData public_jwk); + [Throws=Web5RustCoreError] + JwkData import_private_jwk(JwkData private_key); + KeyManager get_as_key_manager(); +}; + dictionary BearerDidData { DidData did; DocumentData document; @@ -449,6 +467,7 @@ dictionary FilterData { interface PresentationDefinition { constructor(PresentationDefinitionData data); + PresentationDefinitionData get_data(); [Throws=Web5RustCoreError] sequence select_credentials([ByRef] sequence vc_jwts); }; \ No newline at end of file diff --git a/bound/kt/pom.xml b/bound/kt/pom.xml index 9399ee87..1df40f52 100644 --- a/bound/kt/pom.xml +++ b/bound/kt/pom.xml @@ -51,6 +51,11 @@ jackson-annotations 2.17.0 + + com.fasterxml.jackson.module + jackson-module-kotlin + 2.17.0 + diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/Json.kt b/bound/kt/src/main/kotlin/tbdex/sdk/Json.kt new file mode 100644 index 00000000..38605ba3 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/Json.kt @@ -0,0 +1,49 @@ +package tbdex.sdk + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.ObjectWriter +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.module.kotlin.registerKotlinModule + +const val dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + +/** + * A singleton for json serialization/deserialization, shared across the SDK as ObjectMapper instantiation + * is an expensive operation. + * - Serialize ([stringify]) + * - Deserialize ([parse]) + * + * ### Example Usage: + * ```kotlin + * val offering = Json.objectMapper.readValue(payload) + * + * val jsonString = Json.stringify(myObject) + * + * val node = Json.parse(payload) + * ``` + */ +object Json { + /** + * The Jackson object mapper instance, shared across the lib. + * + * It must be public in order for typed parsing to work as we cannot use reified types for Java interop. + */ + val jsonMapper: ObjectMapper = ObjectMapper() + .registerKotlinModule() + .findAndRegisterModules() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + + private val objectWriter: ObjectWriter = jsonMapper.writer() + + /** + * Converts a kotlin object to a json string. + * + * @param obj The object to stringify. + * @return json string. + */ + fun stringify(obj: Any): String { + return objectWriter.writeValueAsString(obj) + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Balances.kt b/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Balances.kt new file mode 100644 index 00000000..33806c26 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Balances.kt @@ -0,0 +1,10 @@ +package tbdex.sdk.httpclient + +import tbdex.sdk.resources.Balance +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.rust.getBalances as rustCoreGetBalances + +fun getBalances(pfiDidUri: String, bearerDid: BearerDid): List { + val rustCoreBalances = rustCoreGetBalances(pfiDidUri, bearerDid.rustCoreBearerDid) + return rustCoreBalances.map { Balance(it) } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Exchanges.kt b/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Exchanges.kt new file mode 100644 index 00000000..2ac53b47 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Exchanges.kt @@ -0,0 +1,51 @@ +package tbdex.sdk.httpclient + +import tbdex.sdk.messages.* +import tbdex.sdk.rust.ExchangeData as RustCoreExchange +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.rust.createExchange as rustCoreCreateExchange +import tbdex.sdk.rust.submitOrder as rustCoreSubmitOrder +import tbdex.sdk.rust.submitClose as rustCoreSubmitClose +import tbdex.sdk.rust.getExchange as rustCoreGetExchange +import tbdex.sdk.rust.getExchanges as rustCoreGetExchanges + +data class Exchange( + val rfq: Rfq, + val quote: Quote? = null, + val order: Order? = null, + val orderStatuses: List? = null, + val close: Close? = null +) { + companion object { + fun fromRustCore(rustCoreExchange: RustCoreExchange): Exchange { + return Exchange( + Rfq(rustCoreExchange.rfq), + rustCoreExchange.quote?.let { Quote(it) }, + rustCoreExchange.order?.let { Order(it) }, + rustCoreExchange.orderStatuses?.let { it -> it.map { OrderStatus(it) } }, + rustCoreExchange.close?.let { Close(it) }, + ) + } + } +} + +fun createExchange(rfq: Rfq, replyTo: String? = null) { + rustCoreCreateExchange(rfq.rustCoreRfq, replyTo) +} + +fun submitOrder(order: Order) { + rustCoreSubmitOrder(order.rustCoreOrder) +} + +fun submitClose(close: Close) { + rustCoreSubmitClose(close.rustCoreClose) +} + +fun getExchange(pfiDidUri: String, bearerDid: BearerDid, exchangeId: String): Exchange { + val rustCoreExchange = rustCoreGetExchange(pfiDidUri, bearerDid.rustCoreBearerDid, exchangeId) + return Exchange.fromRustCore(rustCoreExchange) +} + +fun getExchanges(pfiDidUri: String, bearerDid: BearerDid): List { + return rustCoreGetExchanges(pfiDidUri, bearerDid.rustCoreBearerDid) +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Offerings.kt b/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Offerings.kt new file mode 100644 index 00000000..491c6bde --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/httpclient/Offerings.kt @@ -0,0 +1,9 @@ +package tbdex.sdk.httpclient + +import tbdex.sdk.resources.Offering +import tbdex.sdk.rust.getOfferings as rustCoreGetOfferings + +fun getOfferings(pfiDidUri: String): List { + val rustCoreOfferings = rustCoreGetOfferings(pfiDidUri) + return rustCoreOfferings.map { Offering(it) } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/messages/Close.kt b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Close.kt new file mode 100644 index 00000000..7a5e0234 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Close.kt @@ -0,0 +1,52 @@ +package tbdex.sdk.messages + +import tbdex.sdk.rust.OrderStatus +import tbdex.sdk.rust.Close as RustCoreClose +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.rust.CloseDataData as RustCoreCloseData + +typealias CloseData = RustCoreCloseData + +class Close { + val metadata: MessageMetadata + val data: CloseData + val signature: String + + val rustCoreClose: RustCoreClose + + constructor( + bearerDid: BearerDid, + to: String, + from: String, + exchangeId: String, + data: CloseData, + protocol: String, + externalId: String? = null + ) { + this.rustCoreClose = RustCoreClose(bearerDid.rustCoreBearerDid, to, from, exchangeId, data, protocol, externalId) + + this.metadata = rustCoreClose.getData().metadata + this.data = rustCoreClose.getData().data + this.signature = rustCoreClose.getData().signature + } + + constructor(json: String) { + this.rustCoreClose = RustCoreClose.fromJsonString(json) + + this.metadata = rustCoreClose.getData().metadata + this.data = rustCoreClose.getData().data + this.signature = rustCoreClose.getData().signature + } + + constructor(rustCoreClose: RustCoreClose) { + this.rustCoreClose = rustCoreClose + + this.metadata = rustCoreClose.getData().metadata + this.data = rustCoreClose.getData().data + this.signature = rustCoreClose.getData().signature + } + + fun toJson(): String { + return this.rustCoreClose.toJson() + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/messages/Message.kt b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Message.kt new file mode 100644 index 00000000..c33cbad6 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Message.kt @@ -0,0 +1,8 @@ +package tbdex.sdk.messages + +import tbdex.sdk.rust.MessageKind as RustCoreMessageKind +import tbdex.sdk.rust.MessageMetadataData as RustCoreMetadata + +typealias MessageKind = RustCoreMessageKind + +typealias MessageMetadata = RustCoreMetadata \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/messages/Order.kt b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Order.kt new file mode 100644 index 00000000..0c5ed699 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Order.kt @@ -0,0 +1,43 @@ +package tbdex.sdk.messages + +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.rust.Order as RustCoreOrder + +class Order { + val metadata: MessageMetadata + val signature: String + + val rustCoreOrder: RustCoreOrder + + constructor( + bearerDid: BearerDid, + to: String, + from: String, + exchangeId: String, + protocol: String, + externalId: String? = null + ) { + this.rustCoreOrder = RustCoreOrder(bearerDid.rustCoreBearerDid, to, from, exchangeId, protocol, externalId) + + this.metadata = rustCoreOrder.getData().metadata + this.signature = rustCoreOrder.getData().signature + } + + constructor(json: String) { + this.rustCoreOrder = RustCoreOrder.fromJsonString(json) + + this.metadata = rustCoreOrder.getData().metadata + this.signature = rustCoreOrder.getData().signature + } + + constructor(rustCoreOrder: RustCoreOrder) { + this.rustCoreOrder = rustCoreOrder + + this.metadata = this.rustCoreOrder.getData().metadata + this.signature = this.rustCoreOrder.getData().signature + } + + fun toJson(): String { + return this.rustCoreOrder.toJson() + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/messages/OrderStatus.kt b/bound/kt/src/main/kotlin/tbdex/sdk/messages/OrderStatus.kt new file mode 100644 index 00000000..6ddc22b0 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/messages/OrderStatus.kt @@ -0,0 +1,51 @@ +package tbdex.sdk.messages + +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.rust.OrderStatus as RustCoreOrderStatus +import tbdex.sdk.rust.OrderStatusDataData as RustCoreOrderStatusData + +typealias OrderStatusData = RustCoreOrderStatusData + +class OrderStatus { + val metadata: MessageMetadata + val data: OrderStatusData + val signature: String + + val rustCoreOrderStatus: RustCoreOrderStatus + + constructor( + bearerDid: BearerDid, + to: String, + from: String, + exchangeId: String, + data: OrderStatusData, + protocol: String, + externalId: String? = null + ) { + this.rustCoreOrderStatus = RustCoreOrderStatus(bearerDid.rustCoreBearerDid, to, from, exchangeId, data, protocol, externalId) + + this.metadata = rustCoreOrderStatus.getData().metadata + this.data = rustCoreOrderStatus.getData().data + this.signature = rustCoreOrderStatus.getData().signature + } + + constructor(json: String) { + this.rustCoreOrderStatus = RustCoreOrderStatus.fromJsonString(json) + + this.metadata = rustCoreOrderStatus.getData().metadata + this.data = rustCoreOrderStatus.getData().data + this.signature = rustCoreOrderStatus.getData().signature + } + + constructor(rustCoreOrderStatus: RustCoreOrderStatus) { + this.rustCoreOrderStatus = rustCoreOrderStatus + + this.metadata = this.rustCoreOrderStatus.getData().metadata + this.data = this.rustCoreOrderStatus.getData().data + this.signature = this.rustCoreOrderStatus.getData().signature + } + + fun toJson(): String { + return this.rustCoreOrderStatus.toJson() + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/messages/Quote.kt b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Quote.kt new file mode 100644 index 00000000..282ed1d0 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Quote.kt @@ -0,0 +1,59 @@ +package tbdex.sdk.messages + +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.rust.PaymentInstructionsData as RustCorePaymentInstructions +import tbdex.sdk.rust.QuoteDetailsData as RustCoreQuoteDetails +import tbdex.sdk.rust.Quote as RustCoreQuote +import tbdex.sdk.rust.QuoteDataData as RustCoreQuoteData + +typealias QuoteData = RustCoreQuoteData +typealias QuoteDetails = RustCoreQuoteDetails +typealias PaymentInstructions = RustCorePaymentInstructions + +class Quote { + val metadata: MessageMetadata + val data: QuoteData + val signature: String + + val rustCoreQuote: RustCoreQuote + + constructor( + bearerDid: BearerDid, + to: String, + from: String, + exchangeId: String, + data: QuoteData, + protocol: String, + externalId: String? = null + ) { + this.rustCoreQuote = RustCoreQuote(bearerDid.rustCoreBearerDid, to, from, exchangeId, data, protocol, externalId) + + this.metadata = rustCoreQuote.getData().metadata + this.data = rustCoreQuote.getData().data + this.signature = rustCoreQuote.getData().signature + } + + constructor(json: String) { + this.rustCoreQuote = RustCoreQuote.fromJsonString(json) + + this.metadata = rustCoreQuote.getData().metadata + this.data = QuoteData( + this.rustCoreQuote.getData().data.expiresAt, + this.rustCoreQuote.getData().data.payin, + this.rustCoreQuote.getData().data.payout, + ) + this.signature = rustCoreQuote.getData().signature + } + + constructor(rustCoreQuote: RustCoreQuote) { + this.rustCoreQuote = rustCoreQuote + + this.metadata = this.rustCoreQuote.getData().metadata + this.data = this.rustCoreQuote.getData().data + this.signature = this.rustCoreQuote.getData().signature + } + + fun toJson(): String { + return this.rustCoreQuote.toJson() + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/messages/Rfq.kt b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Rfq.kt new file mode 100644 index 00000000..e151e458 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/messages/Rfq.kt @@ -0,0 +1,200 @@ +package tbdex.sdk.messages + +import com.fasterxml.jackson.databind.JsonNode +import tbdex.sdk.Json +import tbdex.sdk.resources.Offering +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.web5.PresentationDefinition +import tbdex.sdk.rust.Rfq as RustCoreRfq +import tbdex.sdk.rust.CreateRfqDataData as RustCoreCreateRfqData +import tbdex.sdk.rust.CreateSelectedPayinMethodData as RustCoreCreateSelectedPayinMethod +import tbdex.sdk.rust.CreateSelectedPayoutMethodData as RustCoreCreateSelectedPayoutMethod +import tbdex.sdk.rust.RfqDataData as RustCoreRfqData +import tbdex.sdk.rust.SelectedPayinMethodData as RustCoreSelectedPayinMethod +import tbdex.sdk.rust.SelectedPayoutMethodData as RustCoreSelectedPayoutMethod +import tbdex.sdk.rust.RfqPrivateDataData as RustCoreRfqPrivateData +import tbdex.sdk.rust.PrivatePaymentDetailsData as RustCorePrivatePaymentDetails + +class Rfq { + val metadata: MessageMetadata + val data: RfqData + val privateData: RfqPrivateData + val signature: String + + val rustCoreRfq: RustCoreRfq + + constructor( + bearerDid: BearerDid, + to: String, + from: String, + createRfqData: CreateRfqData, + protocol: String, + externalId: String? + ) { + this.rustCoreRfq = RustCoreRfq( + bearerDid.rustCoreBearerDid, to, from, createRfqData.toRustCore(), protocol, externalId + ) + + val rfqData = rustCoreRfq.getData() + this.metadata = rfqData.metadata + this.data = RfqData.fromRustCore(rfqData.data) + this.privateData = RfqPrivateData.fromRustCore(rfqData.privateData) + this.signature = rfqData.signature + } + + constructor(json: String) { + this.rustCoreRfq = RustCoreRfq.fromJsonString(json) + + val rfqData = rustCoreRfq.getData() + this.metadata = rfqData.metadata + this.data = RfqData.fromRustCore(rfqData.data) + this.privateData = RfqPrivateData.fromRustCore(rfqData.privateData) + this.signature = rfqData.signature + } + + constructor(rustCoreRfq: RustCoreRfq) { + this.rustCoreRfq = rustCoreRfq + + val rfqData = this.rustCoreRfq.getData() + this.metadata = rfqData.metadata + this.data = RfqData.fromRustCore(rfqData.data) + this.privateData = RfqPrivateData.fromRustCore(rfqData.privateData) + this.signature = rfqData.signature + } + + fun toJson(): String { + return this.rustCoreRfq.toJson() + } + + fun verifyOfferingRequirements(offering: Offering): Boolean { + return this.rustCoreRfq.verifyOfferingRequirements(offering.rustCoreOffering) + } + + fun verifyAllPrivateData(): Boolean { + return this.rustCoreRfq.verifyAllPrivateData() + } + + fun verifyPresentPrivateData(): Boolean { + return this.rustCoreRfq.verifyPresentPrivateData() + } +} + +data class CreateRfqData( + val offeringId: String, + val payin: CreateSelectedPayinMethod, + val payout: CreateSelectedPayoutMethod, + val claims: List +) { + fun toRustCore(): RustCoreCreateRfqData { + return RustCoreCreateRfqData( + this.offeringId, + this.payin.toRustCore(), + this.payout.toRustCore(), + this.claims + ) + } +} + +data class CreateSelectedPayinMethod( + val kind: String, + val paymentDetails: JsonNode? = null, + val amount: String +) { + fun toRustCore(): RustCoreCreateSelectedPayinMethod { + return RustCoreCreateSelectedPayinMethod( + this.kind, + this.paymentDetails?.let { Json.stringify(it) }, + this.amount + ) + } +} + +data class CreateSelectedPayoutMethod( + val kind: String, + val paymentDetails: JsonNode? = null +) { + fun toRustCore(): RustCoreCreateSelectedPayoutMethod { + return RustCoreCreateSelectedPayoutMethod( + this.kind, + this.paymentDetails?.let { Json.stringify(it) } + ) + } +} + +data class RfqData( + val offeringId: String, + val payin: SelectedPayinMethod, + val payout: SelectedPayoutMethod, + val claimsHash: String? +) { + companion object { + internal fun fromRustCore(rustCoreRfqData: RustCoreRfqData): RfqData { + return RfqData( + rustCoreRfqData.offeringId, + SelectedPayinMethod.fromRustCore(rustCoreRfqData.payin), + SelectedPayoutMethod.fromRustCore(rustCoreRfqData.payout), + rustCoreRfqData.claimsHash + ) + } + } +} + +data class SelectedPayinMethod( + val kind: String, + val paymentDetailsHash: String?, + val amount: String +) { + companion object { + internal fun fromRustCore(rustCoreSelectedPayinMethod: RustCoreSelectedPayinMethod): SelectedPayinMethod { + return SelectedPayinMethod( + rustCoreSelectedPayinMethod.kind, + rustCoreSelectedPayinMethod.paymentDetailsHash, + rustCoreSelectedPayinMethod.amount + ) + } + } +} + +data class SelectedPayoutMethod( + val kind: String, + val paymentDetailsHash: String? +) { + companion object { + internal fun fromRustCore(rustCoreSelectedPayoutMethod: RustCoreSelectedPayoutMethod): SelectedPayoutMethod { + return SelectedPayoutMethod( + rustCoreSelectedPayoutMethod.kind, + rustCoreSelectedPayoutMethod.paymentDetailsHash + ) + } + } +} + +data class RfqPrivateData( + val salt: String, + val payin: PrivatePaymentDetails?, + val payout: PrivatePaymentDetails?, + val claims: List? +) { + companion object { + internal fun fromRustCore(rustCoreRfqPrivateData: RustCoreRfqPrivateData): RfqPrivateData { + return RfqPrivateData( + rustCoreRfqPrivateData.salt, + rustCoreRfqPrivateData.payin?.let { PrivatePaymentDetails.fromRustCore(it) }, + rustCoreRfqPrivateData.payout?.let { PrivatePaymentDetails.fromRustCore(it) }, + rustCoreRfqPrivateData.claims + ) + } + } +} + +data class PrivatePaymentDetails( + val paymentDetails: JsonNode? = null +) { + companion object { + internal fun fromRustCore(rustCorePrivatePaymentDetails: RustCorePrivatePaymentDetails): PrivatePaymentDetails { + return PrivatePaymentDetails( + Json.jsonMapper.readTree(rustCorePrivatePaymentDetails.paymentDetails) + ) + } + } +} diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/resources/Balance.kt b/bound/kt/src/main/kotlin/tbdex/sdk/resources/Balance.kt new file mode 100644 index 00000000..a319a1e1 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/resources/Balance.kt @@ -0,0 +1,51 @@ +package tbdex.sdk.resources + +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.rust.Balance as RustCoreBalance +import tbdex.sdk.rust.BalanceDataData as RustCoreBalanceData + +typealias BalanceData = RustCoreBalanceData + +class Balance { + val metadata: ResourceMetadata + val data: BalanceData + val signature: String + + val rustCoreBalance: RustCoreBalance + + constructor( + bearerDid: BearerDid, + from: String, + data: BalanceData, + signature: String + ) { + this.rustCoreBalance = RustCoreBalance(bearerDid.rustCoreBearerDid, from, data, signature) + + this.metadata = rustCoreBalance.getData().metadata + this.data = rustCoreBalance.getData().data + this.signature = rustCoreBalance.getData().signature + } + + constructor(json: String) { + this.rustCoreBalance = RustCoreBalance.fromJsonString(json) + + this.metadata = rustCoreBalance.getData().metadata + this.data = BalanceData( + this.rustCoreBalance.getData().data.currencyCode, + this.rustCoreBalance.getData().data.available + ) + this.signature = rustCoreBalance.getData().signature + } + + constructor(rustCoreBalance: RustCoreBalance) { + this.rustCoreBalance = rustCoreBalance + + this.metadata = this.rustCoreBalance.getData().metadata + this.data = this.rustCoreBalance.getData().data + this.signature = this.rustCoreBalance.getData().signature + } + + fun toJson(): String { + return this.rustCoreBalance.toJson() + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/resources/Offering.kt b/bound/kt/src/main/kotlin/tbdex/sdk/resources/Offering.kt new file mode 100644 index 00000000..f731bcce --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/resources/Offering.kt @@ -0,0 +1,218 @@ +package tbdex.sdk.resources + +import com.fasterxml.jackson.databind.JsonNode +import tbdex.sdk.Json +import tbdex.sdk.web5.BearerDid +import tbdex.sdk.web5.PresentationDefinition +import tbdex.sdk.rust.Offering as RustCoreOffering +import tbdex.sdk.rust.OfferingDataData as RustCoreOfferingData +import tbdex.sdk.rust.PayinDetailsData as RustCorePayinDetails +import tbdex.sdk.rust.PayinMethodData as RustCorePayinMethod +import tbdex.sdk.rust.PayoutDetailsData as RustCorePayoutDetails +import tbdex.sdk.rust.PayoutMethodData as RustCorePayoutMethod + +class Offering { + val metadata: ResourceMetadata + val data: OfferingData + val signature: String + + val rustCoreOffering: RustCoreOffering + + constructor( + bearerDid: BearerDid, + from: String, + data: OfferingData, + protocol: String + ) { + this.rustCoreOffering = RustCoreOffering(bearerDid.rustCoreBearerDid, from, data.toRustCore(), protocol) + + this.metadata = rustCoreOffering.getData().metadata + this.data = OfferingData.fromRustCore(rustCoreOffering.getData().data) + this.signature = rustCoreOffering.getData().signature + } + + constructor(json: String) { + this.rustCoreOffering = RustCoreOffering.fromJsonString(json) + + this.metadata = rustCoreOffering.getData().metadata + this.data = OfferingData.fromRustCore(rustCoreOffering.getData().data) + this.signature = rustCoreOffering.getData().signature + } + + constructor(rustCoreOffering: RustCoreOffering) { + this.rustCoreOffering = rustCoreOffering + + this.metadata = this.rustCoreOffering.getData().metadata + this.data = OfferingData.fromRustCore(this.rustCoreOffering.getData().data) + this.signature = this.rustCoreOffering.getData().signature + } + + fun toJson(): String { + return this.rustCoreOffering.toJson() + } +} + +data class OfferingData( + val description: String, + val payoutUnitsPerPayinUnit: String, + val payin: PayinDetails, + val payout: PayoutDetails, + val requiredClaims: PresentationDefinition? +) { + fun toRustCore(): RustCoreOfferingData { + return RustCoreOfferingData( + this.description, + this.payoutUnitsPerPayinUnit, + this.payin.toRustCore(), + this.payout.toRustCore(), + this.requiredClaims?.rustCorePresentationDefinition?.getData() + ) + } + + companion object { + internal fun fromRustCore (rustCoreOfferingData: RustCoreOfferingData): OfferingData { + return OfferingData( + rustCoreOfferingData.description, + rustCoreOfferingData.payoutUnitsPerPayinUnit, + PayinDetails.fromRustCore(rustCoreOfferingData.payin), + PayoutDetails.fromRustCore(rustCoreOfferingData.payout), + rustCoreOfferingData.requiredClaims?.let { PresentationDefinition(it) } + ) + } + } +} + +data class PayinDetails( + val currencyCode: String, + val min: String? = null, + val max: String? = null, + val methods: List +) { + fun toRustCore(): RustCorePayinDetails { + return RustCorePayinDetails( + this.currencyCode, + this.min, + this.max, + this.methods.map { it.toRustCore() } + ) + } + + companion object { + internal fun fromRustCore(rustCorePayinDetails: RustCorePayinDetails): PayinDetails { + return PayinDetails( + rustCorePayinDetails.currencyCode, + rustCorePayinDetails.min, + rustCorePayinDetails.max, + rustCorePayinDetails.methods.map { PayinMethod.fromRustCore(it) } + ) + } + } +} + +data class PayinMethod( + val kind: String, + val name: String? = null, + val description: String? = null, + val group: String? = null, + val requiredPaymentDetails: JsonNode? = null, + val fee: String? = null, + val min: String? = null, + val max: String? = null +) { + fun toRustCore(): RustCorePayinMethod { + return RustCorePayinMethod( + this.kind, + this.name, + this.description, + this.group, + this.requiredPaymentDetails?.let { Json.stringify(it) }, + this.fee, + this.min, + this.max + ) + } + + companion object { + internal fun fromRustCore(rustCorePayinMethod: RustCorePayinMethod): PayinMethod { + return PayinMethod( + rustCorePayinMethod.kind, + rustCorePayinMethod.name, + rustCorePayinMethod.description, + rustCorePayinMethod.group, + rustCorePayinMethod.requiredPaymentDetails?.let { Json.jsonMapper.readTree(it) }, + rustCorePayinMethod.fee, + rustCorePayinMethod.min, + rustCorePayinMethod.max, + ) + } + } +} + +data class PayoutDetails( + val currencyCode: String, + val min: String? = null, + val max: String? = null, + val methods: List +) { + fun toRustCore(): RustCorePayoutDetails { + return RustCorePayoutDetails( + this.currencyCode, + this.min, + this.max, + this.methods.map { it.toRustCore() } + ) + } + + companion object { + internal fun fromRustCore(rustCorePayoutDetails: RustCorePayoutDetails): PayoutDetails { + return PayoutDetails( + rustCorePayoutDetails.currencyCode, + rustCorePayoutDetails.min, + rustCorePayoutDetails.max, + rustCorePayoutDetails.methods.map { PayoutMethod.fromRustCore(it) } + ) + } + } +} + +data class PayoutMethod( + val kind: String, + val name: String? = null, + val description: String? = null, + val group: String? = null, + val requiredPaymentDetails: JsonNode? = null, + val fee: String? = null, + val min: String? = null, + val max: String? = null, + val estimatedSettlementTime: Long +) { + fun toRustCore(): RustCorePayoutMethod { + return RustCorePayoutMethod( + this.kind, + this.name, + this.description, + this.group, + this.requiredPaymentDetails?.let { Json.stringify(it) }, + this.fee, + this.min, + this.max, + this.estimatedSettlementTime + ) + } + + companion object { + internal fun fromRustCore(rustCorePayoutMethod: RustCorePayoutMethod): PayoutMethod { + return PayoutMethod( + rustCorePayoutMethod.kind, + rustCorePayoutMethod.name, + rustCorePayoutMethod.description, + rustCorePayoutMethod.group, + rustCorePayoutMethod.requiredPaymentDetails?.let { Json.jsonMapper.readTree(it) }, + rustCorePayoutMethod.fee, + rustCorePayoutMethod.min, + rustCorePayoutMethod.max, + rustCorePayoutMethod.estimatedSettlementTime + ) + } + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/resources/Resource.kt b/bound/kt/src/main/kotlin/tbdex/sdk/resources/Resource.kt new file mode 100644 index 00000000..ee5afb45 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/resources/Resource.kt @@ -0,0 +1,8 @@ +package tbdex.sdk.resources + +import tbdex.sdk.rust.ResourceKind as RustCoreResourceKind +import tbdex.sdk.rust.ResourceMetadataData as RustCoreResourceMetadata + +typealias ResourceKind = RustCoreResourceKind + +typealias ResourceMetadata = RustCoreResourceMetadata \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/rust/tbdex.kt b/bound/kt/src/main/kotlin/tbdex/sdk/rust/tbdex.kt index d3ac2ea2..ef64fc76 100644 --- a/bound/kt/src/main/kotlin/tbdex/sdk/rust/tbdex.kt +++ b/bound/kt/src/main/kotlin/tbdex/sdk/rust/tbdex.kt @@ -30,6 +30,7 @@ import java.nio.CharBuffer import java.nio.charset.CodingErrorAction import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean import java.nio.file.Files // This is a helper for safely working with byte buffers returned from the Rust code. @@ -633,6 +634,184 @@ internal open class UniffiForeignFutureStructVoid( internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructVoid.UniffiByValue,) } +internal interface UniffiCallbackInterfaceKeyManagerMethod0 : com.sun.jna.Callback { + fun callback(`uniffiHandle`: Long,`publicJwk`: RustBuffer.ByValue,`uniffiOutReturn`: PointerByReference,uniffiCallStatus: UniffiRustCallStatus,) +} +internal interface UniffiCallbackInterfaceSignerMethod0 : com.sun.jna.Callback { + fun callback(`uniffiHandle`: Long,`payload`: RustBuffer.ByValue,`uniffiOutReturn`: RustBuffer,uniffiCallStatus: UniffiRustCallStatus,) +} +@Structure.FieldOrder("getSigner", "uniffiFree") +internal open class UniffiVTableCallbackInterfaceKeyManager( + @JvmField internal var `getSigner`: UniffiCallbackInterfaceKeyManagerMethod0? = null, + @JvmField internal var `uniffiFree`: UniffiCallbackInterfaceFree? = null, +) : Structure() { + class UniffiByValue( + `getSigner`: UniffiCallbackInterfaceKeyManagerMethod0? = null, + `uniffiFree`: UniffiCallbackInterfaceFree? = null, + ): UniffiVTableCallbackInterfaceKeyManager(`getSigner`,`uniffiFree`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiVTableCallbackInterfaceKeyManager) { + `getSigner` = other.`getSigner` + `uniffiFree` = other.`uniffiFree` + } + +} +@Structure.FieldOrder("sign", "uniffiFree") +internal open class UniffiVTableCallbackInterfaceSigner( + @JvmField internal var `sign`: UniffiCallbackInterfaceSignerMethod0? = null, + @JvmField internal var `uniffiFree`: UniffiCallbackInterfaceFree? = null, +) : Structure() { + class UniffiByValue( + `sign`: UniffiCallbackInterfaceSignerMethod0? = null, + `uniffiFree`: UniffiCallbackInterfaceFree? = null, + ): UniffiVTableCallbackInterfaceSigner(`sign`,`uniffiFree`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiVTableCallbackInterfaceSigner) { + `sign` = other.`sign` + `uniffiFree` = other.`uniffiFree` + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -711,10 +890,182 @@ internal interface UniffiLib : Library { } } + // The Cleaner for the whole library + internal val CLEANER: UniffiCleaner by lazy { + UniffiCleaner.create() + } } - fun uniffi_tbdex_uniffi_fn_func_hello_world(uniffi_out_err: UniffiRustCallStatus, + fun uniffi_tbdex_uniffi_fn_clone_balance(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_balance(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_balance_from_json_string(`json`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_constructor_balance_new(`bearerDid`: Pointer,`from`: RustBuffer.ByValue,`data`: RustBuffer.ByValue,`protocol`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_balance_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_balance_to_json(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_bearerdid(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_bearerdid(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_bearerdid_new(`uri`: RustBuffer.ByValue,`keyManager`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_bearerdid_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_bearerdid_get_signer(`ptr`: Pointer,`keyId`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_clone_close(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_close(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_close_from_json_string(`json`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_constructor_close_new(`bearerDid`: Pointer,`to`: RustBuffer.ByValue,`from`: RustBuffer.ByValue,`exchangeId`: RustBuffer.ByValue,`data`: RustBuffer.ByValue,`protocol`: RustBuffer.ByValue,`externalId`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_close_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_close_to_json(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_inmemorykeymanager(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_inmemorykeymanager(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_inmemorykeymanager_new(uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_inmemorykeymanager_get_as_key_manager(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_inmemorykeymanager_get_signer(`ptr`: Pointer,`publicJwk`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_inmemorykeymanager_import_private_jwk(`ptr`: Pointer,`privateKey`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_keymanager(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_keymanager(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_method_keymanager_get_signer(`ptr`: Pointer,`publicJwk`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_clone_offering(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_offering(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_offering_from_json_string(`json`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_constructor_offering_new(`bearerDid`: Pointer,`from`: RustBuffer.ByValue,`data`: RustBuffer.ByValue,`protocol`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_offering_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_offering_to_json(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_order(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_order(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_order_from_json_string(`json`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_constructor_order_new(`bearerDid`: Pointer,`to`: RustBuffer.ByValue,`from`: RustBuffer.ByValue,`exchangeId`: RustBuffer.ByValue,`protocol`: RustBuffer.ByValue,`externalId`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_order_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_order_to_json(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_orderstatus(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_orderstatus(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_orderstatus_from_json_string(`json`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_constructor_orderstatus_new(`bearerDid`: Pointer,`to`: RustBuffer.ByValue,`from`: RustBuffer.ByValue,`exchangeId`: RustBuffer.ByValue,`data`: RustBuffer.ByValue,`protocol`: RustBuffer.ByValue,`externalId`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_orderstatus_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_orderstatus_to_json(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_presentationdefinition(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_presentationdefinition(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_presentationdefinition_new(`data`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_presentationdefinition_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_presentationdefinition_select_credentials(`ptr`: Pointer,`vcJwts`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_quote(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_quote(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_quote_from_json_string(`json`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_constructor_quote_new(`bearerDid`: Pointer,`to`: RustBuffer.ByValue,`from`: RustBuffer.ByValue,`exchangeId`: RustBuffer.ByValue,`data`: RustBuffer.ByValue,`protocol`: RustBuffer.ByValue,`externalId`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_quote_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_quote_to_json(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_rfq(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_rfq(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_constructor_rfq_from_json_string(`json`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_constructor_rfq_new(`bearerDid`: Pointer,`to`: RustBuffer.ByValue,`from`: RustBuffer.ByValue,`createRfqData`: RustBuffer.ByValue,`protocol`: RustBuffer.ByValue,`externalId`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_method_rfq_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_rfq_to_json(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_rfq_verify_all_private_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Byte + fun uniffi_tbdex_uniffi_fn_method_rfq_verify_offering_requirements(`ptr`: Pointer,`offering`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Byte + fun uniffi_tbdex_uniffi_fn_method_rfq_verify_present_private_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Byte + fun uniffi_tbdex_uniffi_fn_clone_rustcoreerror(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_rustcoreerror(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_method_rustcoreerror_error_type(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_rustcoreerror_message(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_rustcoreerror_variant(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_signer(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_signer(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_method_signer_sign(`ptr`: Pointer,`payload`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_clone_web5rustcoreerror(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer + fun uniffi_tbdex_uniffi_fn_free_web5rustcoreerror(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_method_web5rustcoreerror_error_type(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_web5rustcoreerror_message(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_method_web5rustcoreerror_variant(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_func_create_exchange(`rfq`: Pointer,`replyTo`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Unit + fun uniffi_tbdex_uniffi_fn_func_get_balances(`pfiDidUri`: RustBuffer.ByValue,`bearerDid`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_func_get_exchange(`pfiDidUri`: RustBuffer.ByValue,`bearerDid`: Pointer,`exchangeId`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_func_get_exchanges(`pfiDidUri`: RustBuffer.ByValue,`bearerDid`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_func_get_offerings(`pfiDidUri`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_tbdex_uniffi_fn_func_submit_close(`close`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_func_submit_order(`order`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_tbdex_uniffi_fn_func_tmp_hack_bearer_did(`did`: RustBuffer.ByValue,`document`: RustBuffer.ByValue,`keyManager`: Pointer,uniffi_out_err: UniffiRustCallStatus, + ): Pointer fun ffi_tbdex_uniffi_rustbuffer_alloc(`size`: Long,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue fun ffi_tbdex_uniffi_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,uniffi_out_err: UniffiRustCallStatus, @@ -827,26 +1178,306 @@ internal interface UniffiLib : Library { ): Unit fun ffi_tbdex_uniffi_rust_future_complete_void(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, ): Unit - fun uniffi_tbdex_uniffi_checksum_func_hello_world( + fun uniffi_tbdex_uniffi_checksum_func_create_exchange( ): Short - fun ffi_tbdex_uniffi_uniffi_contract_version( - ): Int - -} - -private fun uniffiCheckContractApiVersion(lib: UniffiLib) { - // Get the bindings contract version from our ComponentInterface - val bindings_contract_version = 26 - // Get the scaffolding contract version by calling the into the dylib - val scaffolding_contract_version = lib.ffi_tbdex_uniffi_uniffi_contract_version() - if (bindings_contract_version != scaffolding_contract_version) { - throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") - } -} - -@Suppress("UNUSED_PARAMETER") -private fun uniffiCheckApiChecksums(lib: UniffiLib) { - if (lib.uniffi_tbdex_uniffi_checksum_func_hello_world() != 12574.toShort()) { + fun uniffi_tbdex_uniffi_checksum_func_get_balances( + ): Short + fun uniffi_tbdex_uniffi_checksum_func_get_exchange( + ): Short + fun uniffi_tbdex_uniffi_checksum_func_get_exchanges( + ): Short + fun uniffi_tbdex_uniffi_checksum_func_get_offerings( + ): Short + fun uniffi_tbdex_uniffi_checksum_func_submit_close( + ): Short + fun uniffi_tbdex_uniffi_checksum_func_submit_order( + ): Short + fun uniffi_tbdex_uniffi_checksum_func_tmp_hack_bearer_did( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_balance_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_balance_to_json( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_bearerdid_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_bearerdid_get_signer( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_close_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_close_to_json( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_inmemorykeymanager_get_as_key_manager( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_inmemorykeymanager_get_signer( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_inmemorykeymanager_import_private_jwk( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_keymanager_get_signer( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_offering_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_offering_to_json( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_order_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_order_to_json( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_orderstatus_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_orderstatus_to_json( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_presentationdefinition_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_presentationdefinition_select_credentials( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_quote_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_quote_to_json( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_rfq_get_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_rfq_to_json( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_rfq_verify_all_private_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_rfq_verify_offering_requirements( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_rfq_verify_present_private_data( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_rustcoreerror_error_type( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_rustcoreerror_message( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_rustcoreerror_variant( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_signer_sign( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_web5rustcoreerror_error_type( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_web5rustcoreerror_message( + ): Short + fun uniffi_tbdex_uniffi_checksum_method_web5rustcoreerror_variant( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_balance_from_json_string( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_balance_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_bearerdid_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_close_from_json_string( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_close_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_inmemorykeymanager_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_offering_from_json_string( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_offering_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_order_from_json_string( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_order_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_orderstatus_from_json_string( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_orderstatus_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_presentationdefinition_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_quote_from_json_string( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_quote_new( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_rfq_from_json_string( + ): Short + fun uniffi_tbdex_uniffi_checksum_constructor_rfq_new( + ): Short + fun ffi_tbdex_uniffi_uniffi_contract_version( + ): Int + +} + +private fun uniffiCheckContractApiVersion(lib: UniffiLib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = 26 + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.ffi_tbdex_uniffi_uniffi_contract_version() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} + +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: UniffiLib) { + if (lib.uniffi_tbdex_uniffi_checksum_func_create_exchange() != 7308.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_func_get_balances() != 51715.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_func_get_exchange() != 12075.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_func_get_exchanges() != 41220.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_func_get_offerings() != 20626.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_func_submit_close() != 11219.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_func_submit_order() != 22522.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_func_tmp_hack_bearer_did() != 51792.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_balance_get_data() != 45636.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_balance_to_json() != 2963.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_bearerdid_get_data() != 7101.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_bearerdid_get_signer() != 48151.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_close_get_data() != 42568.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_close_to_json() != 63038.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_inmemorykeymanager_get_as_key_manager() != 4734.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_inmemorykeymanager_get_signer() != 53772.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_inmemorykeymanager_import_private_jwk() != 48963.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_keymanager_get_signer() != 33675.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_offering_get_data() != 54801.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_offering_to_json() != 24105.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_order_get_data() != 46414.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_order_to_json() != 64999.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_orderstatus_get_data() != 32040.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_orderstatus_to_json() != 42316.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_presentationdefinition_get_data() != 50448.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_presentationdefinition_select_credentials() != 2690.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_quote_get_data() != 23104.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_quote_to_json() != 20930.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_rfq_get_data() != 45129.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_rfq_to_json() != 56973.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_rfq_verify_all_private_data() != 54169.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_rfq_verify_offering_requirements() != 64928.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_rfq_verify_present_private_data() != 57252.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_rustcoreerror_error_type() != 26968.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_rustcoreerror_message() != 41308.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_rustcoreerror_variant() != 585.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_signer_sign() != 20840.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_web5rustcoreerror_error_type() != 50948.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_web5rustcoreerror_message() != 54823.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_method_web5rustcoreerror_variant() != 12994.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_balance_from_json_string() != 47556.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_balance_new() != 49171.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_bearerdid_new() != 8830.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_close_from_json_string() != 64811.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_close_new() != 7037.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_inmemorykeymanager_new() != 52263.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_offering_from_json_string() != 21018.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_offering_new() != 30449.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_order_from_json_string() != 63305.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_order_new() != 26328.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_orderstatus_from_json_string() != 11447.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_orderstatus_new() != 5401.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_presentationdefinition_new() != 49889.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_quote_from_json_string() != 59989.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_quote_new() != 24782.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_rfq_from_json_string() != 27704.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_tbdex_uniffi_checksum_constructor_rfq_new() != 39566.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } } @@ -889,6 +1520,66 @@ inline fun T.use(block: (T) -> R) = /** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */ object NoPointer +public object FfiConverterUByte: FfiConverter { + override fun lift(value: Byte): UByte { + return value.toUByte() + } + + override fun read(buf: ByteBuffer): UByte { + return lift(buf.get()) + } + + override fun lower(value: UByte): Byte { + return value.toByte() + } + + override fun allocationSize(value: UByte) = 1UL + + override fun write(value: UByte, buf: ByteBuffer) { + buf.put(value.toByte()) + } +} + +public object FfiConverterLong: FfiConverter { + override fun lift(value: Long): Long { + return value + } + + override fun read(buf: ByteBuffer): Long { + return buf.getLong() + } + + override fun lower(value: Long): Long { + return value + } + + override fun allocationSize(value: Long) = 8UL + + override fun write(value: Long, buf: ByteBuffer) { + buf.putLong(value) + } +} + +public object FfiConverterBoolean: FfiConverter { + override fun lift(value: Byte): Boolean { + return value.toInt() != 0 + } + + override fun read(buf: ByteBuffer): Boolean { + return lift(buf.get()) + } + + override fun lower(value: Boolean): Byte { + return if (value) 1.toByte() else 0.toByte() + } + + override fun allocationSize(value: Boolean) = 1UL + + override fun write(value: Boolean, buf: ByteBuffer) { + buf.put(lower(value)) + } +} + public object FfiConverterString: FfiConverter { // Note: we don't inherit from FfiConverterRustBuffer, because we use a // special encoding when lowering/lifting. We can use `RustBuffer.len` to @@ -941,13 +1632,6188 @@ public object FfiConverterString: FfiConverter { buf.putInt(byteBuf.limit()) buf.put(byteBuf) } -} fun `helloWorld`() - = - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_hello_world( - _status) } - + +public object FfiConverterByteArray: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ByteArray { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr + } + override fun allocationSize(value: ByteArray): ULong { + return 4UL + value.size.toULong() + } + override fun write(value: ByteArray, buf: ByteBuffer) { + buf.putInt(value.size) + buf.put(value) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +// The cleaner interface for Object finalization code to run. +// This is the entry point to any implementation that we're using. +// +// The cleaner registers objects and returns cleanables, so now we are +// defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the +// different implmentations available at compile time. +interface UniffiCleaner { + interface Cleanable { + fun clean() + } + + fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable + + companion object +} + +// The fallback Jna cleaner, which is available for both Android, and the JVM. +private class UniffiJnaCleaner : UniffiCleaner { + private val cleaner = com.sun.jna.internal.Cleaner.getCleaner() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + UniffiJnaCleanable(cleaner.register(value, cleanUpTask)) +} + +private class UniffiJnaCleanable( + private val cleanable: com.sun.jna.internal.Cleaner.Cleanable, +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} + +// We decide at uniffi binding generation time whether we were +// using Android or not. +// There are further runtime checks to chose the correct implementation +// of the cleaner. +private fun UniffiCleaner.Companion.create(): UniffiCleaner = + try { + // For safety's sake: if the library hasn't been run in android_cleaner = true + // mode, but is being run on Android, then we still need to think about + // Android API versions. + // So we check if java.lang.ref.Cleaner is there, and use that… + java.lang.Class.forName("java.lang.ref.Cleaner") + JavaLangRefCleaner() + } catch (e: ClassNotFoundException) { + // … otherwise, fallback to the JNA cleaner. + UniffiJnaCleaner() + } + +private class JavaLangRefCleaner : UniffiCleaner { + val cleaner = java.lang.ref.Cleaner.create() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + JavaLangRefCleanable(cleaner.register(value, cleanUpTask)) +} + +private class JavaLangRefCleanable( + val cleanable: java.lang.ref.Cleaner.Cleanable +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} +public interface BalanceInterface { + + fun `getData`(): BalanceData + + fun `toJson`(): kotlin.String + + companion object +} + +open class Balance: Disposable, AutoCloseable, BalanceInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`bearerDid`: BearerDid, `from`: kotlin.String, `data`: BalanceDataData, `protocol`: kotlin.String) : + this( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_balance_new( + FfiConverterTypeBearerDid.lower(`bearerDid`),FfiConverterString.lower(`from`),FfiConverterTypeBalanceDataData.lower(`data`),FfiConverterString.lower(`protocol`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_balance(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_balance(pointer!!, status) + } + } + + + @Throws(RustCoreException::class)override fun `getData`(): BalanceData { + return FfiConverterTypeBalanceData.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_balance_get_data( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `toJson`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_balance_to_json( + it, _status) +} + } + ) + } + + + + + + companion object { + + @Throws(RustCoreException::class) fun `fromJsonString`(`json`: kotlin.String): Balance { + return FfiConverterTypeBalance.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_balance_from_json_string( + FfiConverterString.lower(`json`),_status) +} + ) + } + + + + } + +} + +public object FfiConverterTypeBalance: FfiConverter { + + override fun lower(value: Balance): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Balance { + return Balance(value) + } + + override fun read(buf: ByteBuffer): Balance { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Balance) = 8UL + + override fun write(value: Balance, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface BearerDidInterface { + + fun `getData`(): BearerDidData + + fun `getSigner`(`keyId`: kotlin.String): Signer + + companion object +} + +open class BearerDid: Disposable, AutoCloseable, BearerDidInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`uri`: kotlin.String, `keyManager`: KeyManager) : + this( + uniffiRustCallWithError(Web5RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_bearerdid_new( + FfiConverterString.lower(`uri`),FfiConverterTypeKeyManager.lower(`keyManager`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_bearerdid(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_bearerdid(pointer!!, status) + } + } + + override fun `getData`(): BearerDidData { + return FfiConverterTypeBearerDidData.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_bearerdid_get_data( + it, _status) +} + } + ) + } + + + + @Throws(Web5RustCoreException::class)override fun `getSigner`(`keyId`: kotlin.String): Signer { + return FfiConverterTypeSigner.lift( + callWithPointer { + uniffiRustCallWithError(Web5RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_bearerdid_get_signer( + it, FfiConverterString.lower(`keyId`),_status) +} + } + ) + } + + + + + + + companion object + +} + +public object FfiConverterTypeBearerDid: FfiConverter { + + override fun lower(value: BearerDid): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): BearerDid { + return BearerDid(value) + } + + override fun read(buf: ByteBuffer): BearerDid { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: BearerDid) = 8UL + + override fun write(value: BearerDid, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface CloseInterface { + + fun `getData`(): CloseData + + fun `toJson`(): kotlin.String + + companion object +} + +open class Close: Disposable, AutoCloseable, CloseInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`bearerDid`: BearerDid, `to`: kotlin.String, `from`: kotlin.String, `exchangeId`: kotlin.String, `data`: CloseDataData, `protocol`: kotlin.String, `externalId`: kotlin.String?) : + this( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_close_new( + FfiConverterTypeBearerDid.lower(`bearerDid`),FfiConverterString.lower(`to`),FfiConverterString.lower(`from`),FfiConverterString.lower(`exchangeId`),FfiConverterTypeCloseDataData.lower(`data`),FfiConverterString.lower(`protocol`),FfiConverterOptionalString.lower(`externalId`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_close(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_close(pointer!!, status) + } + } + + + @Throws(RustCoreException::class)override fun `getData`(): CloseData { + return FfiConverterTypeCloseData.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_close_get_data( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `toJson`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_close_to_json( + it, _status) +} + } + ) + } + + + + + + companion object { + + @Throws(RustCoreException::class) fun `fromJsonString`(`json`: kotlin.String): Close { + return FfiConverterTypeClose.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_close_from_json_string( + FfiConverterString.lower(`json`),_status) +} + ) + } + + + + } + +} + +public object FfiConverterTypeClose: FfiConverter { + + override fun lower(value: Close): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Close { + return Close(value) + } + + override fun read(buf: ByteBuffer): Close { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Close) = 8UL + + override fun write(value: Close, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface InMemoryKeyManagerInterface { + + fun `getAsKeyManager`(): KeyManager + + fun `getSigner`(`publicJwk`: JwkData): Signer + + fun `importPrivateJwk`(`privateKey`: JwkData): JwkData + + companion object +} + +open class InMemoryKeyManager: Disposable, AutoCloseable, InMemoryKeyManagerInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor() : + this( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_inmemorykeymanager_new( + _status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_inmemorykeymanager(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_inmemorykeymanager(pointer!!, status) + } + } + + override fun `getAsKeyManager`(): KeyManager { + return FfiConverterTypeKeyManager.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_inmemorykeymanager_get_as_key_manager( + it, _status) +} + } + ) + } + + + + @Throws(Web5RustCoreException::class)override fun `getSigner`(`publicJwk`: JwkData): Signer { + return FfiConverterTypeSigner.lift( + callWithPointer { + uniffiRustCallWithError(Web5RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_inmemorykeymanager_get_signer( + it, FfiConverterTypeJwkData.lower(`publicJwk`),_status) +} + } + ) + } + + + + @Throws(Web5RustCoreException::class)override fun `importPrivateJwk`(`privateKey`: JwkData): JwkData { + return FfiConverterTypeJwkData.lift( + callWithPointer { + uniffiRustCallWithError(Web5RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_inmemorykeymanager_import_private_jwk( + it, FfiConverterTypeJwkData.lower(`privateKey`),_status) +} + } + ) + } + + + + + + + companion object + +} + +public object FfiConverterTypeInMemoryKeyManager: FfiConverter { + + override fun lower(value: InMemoryKeyManager): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): InMemoryKeyManager { + return InMemoryKeyManager(value) + } + + override fun read(buf: ByteBuffer): InMemoryKeyManager { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: InMemoryKeyManager) = 8UL + + override fun write(value: InMemoryKeyManager, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface KeyManagerInterface { + + fun `getSigner`(`publicJwk`: JwkData): Signer + + companion object +} + +open class KeyManager: Disposable, AutoCloseable, KeyManagerInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_keymanager(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_keymanager(pointer!!, status) + } + } + + + @Throws(Web5RustCoreException::class)override fun `getSigner`(`publicJwk`: JwkData): Signer { + return FfiConverterTypeSigner.lift( + callWithPointer { + uniffiRustCallWithError(Web5RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_keymanager_get_signer( + it, FfiConverterTypeJwkData.lower(`publicJwk`),_status) +} + } + ) + } + + + + + + + companion object + +} + +public object FfiConverterTypeKeyManager: FfiConverter { + + override fun lower(value: KeyManager): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): KeyManager { + return KeyManager(value) + } + + override fun read(buf: ByteBuffer): KeyManager { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: KeyManager) = 8UL + + override fun write(value: KeyManager, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface OfferingInterface { + + fun `getData`(): OfferingData + + fun `toJson`(): kotlin.String + + companion object +} + +open class Offering: Disposable, AutoCloseable, OfferingInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`bearerDid`: BearerDid, `from`: kotlin.String, `data`: OfferingDataData, `protocol`: kotlin.String) : + this( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_offering_new( + FfiConverterTypeBearerDid.lower(`bearerDid`),FfiConverterString.lower(`from`),FfiConverterTypeOfferingDataData.lower(`data`),FfiConverterString.lower(`protocol`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_offering(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_offering(pointer!!, status) + } + } + + + @Throws(RustCoreException::class)override fun `getData`(): OfferingData { + return FfiConverterTypeOfferingData.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_offering_get_data( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `toJson`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_offering_to_json( + it, _status) +} + } + ) + } + + + + + + companion object { + + @Throws(RustCoreException::class) fun `fromJsonString`(`json`: kotlin.String): Offering { + return FfiConverterTypeOffering.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_offering_from_json_string( + FfiConverterString.lower(`json`),_status) +} + ) + } + + + + } + +} + +public object FfiConverterTypeOffering: FfiConverter { + + override fun lower(value: Offering): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Offering { + return Offering(value) + } + + override fun read(buf: ByteBuffer): Offering { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Offering) = 8UL + + override fun write(value: Offering, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface OrderInterface { + + fun `getData`(): OrderData + + fun `toJson`(): kotlin.String + + companion object +} + +open class Order: Disposable, AutoCloseable, OrderInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`bearerDid`: BearerDid, `to`: kotlin.String, `from`: kotlin.String, `exchangeId`: kotlin.String, `protocol`: kotlin.String, `externalId`: kotlin.String?) : + this( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_order_new( + FfiConverterTypeBearerDid.lower(`bearerDid`),FfiConverterString.lower(`to`),FfiConverterString.lower(`from`),FfiConverterString.lower(`exchangeId`),FfiConverterString.lower(`protocol`),FfiConverterOptionalString.lower(`externalId`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_order(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_order(pointer!!, status) + } + } + + + @Throws(RustCoreException::class)override fun `getData`(): OrderData { + return FfiConverterTypeOrderData.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_order_get_data( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `toJson`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_order_to_json( + it, _status) +} + } + ) + } + + + + + + companion object { + + @Throws(RustCoreException::class) fun `fromJsonString`(`json`: kotlin.String): Order { + return FfiConverterTypeOrder.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_order_from_json_string( + FfiConverterString.lower(`json`),_status) +} + ) + } + + + + } + +} + +public object FfiConverterTypeOrder: FfiConverter { + + override fun lower(value: Order): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Order { + return Order(value) + } + + override fun read(buf: ByteBuffer): Order { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Order) = 8UL + + override fun write(value: Order, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface OrderStatusInterface { + + fun `getData`(): OrderStatusData + + fun `toJson`(): kotlin.String + + companion object +} + +open class OrderStatus: Disposable, AutoCloseable, OrderStatusInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`bearerDid`: BearerDid, `to`: kotlin.String, `from`: kotlin.String, `exchangeId`: kotlin.String, `data`: OrderStatusDataData, `protocol`: kotlin.String, `externalId`: kotlin.String?) : + this( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_orderstatus_new( + FfiConverterTypeBearerDid.lower(`bearerDid`),FfiConverterString.lower(`to`),FfiConverterString.lower(`from`),FfiConverterString.lower(`exchangeId`),FfiConverterTypeOrderStatusDataData.lower(`data`),FfiConverterString.lower(`protocol`),FfiConverterOptionalString.lower(`externalId`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_orderstatus(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_orderstatus(pointer!!, status) + } + } + + + @Throws(RustCoreException::class)override fun `getData`(): OrderStatusData { + return FfiConverterTypeOrderStatusData.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_orderstatus_get_data( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `toJson`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_orderstatus_to_json( + it, _status) +} + } + ) + } + + + + + + companion object { + + @Throws(RustCoreException::class) fun `fromJsonString`(`json`: kotlin.String): OrderStatus { + return FfiConverterTypeOrderStatus.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_orderstatus_from_json_string( + FfiConverterString.lower(`json`),_status) +} + ) + } + + + + } + +} + +public object FfiConverterTypeOrderStatus: FfiConverter { + + override fun lower(value: OrderStatus): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): OrderStatus { + return OrderStatus(value) + } + + override fun read(buf: ByteBuffer): OrderStatus { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: OrderStatus) = 8UL + + override fun write(value: OrderStatus, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface PresentationDefinitionInterface { + + fun `getData`(): PresentationDefinitionData + + fun `selectCredentials`(`vcJwts`: List): List + + companion object +} + +open class PresentationDefinition: Disposable, AutoCloseable, PresentationDefinitionInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`data`: PresentationDefinitionData) : + this( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_presentationdefinition_new( + FfiConverterTypePresentationDefinitionData.lower(`data`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_presentationdefinition(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_presentationdefinition(pointer!!, status) + } + } + + override fun `getData`(): PresentationDefinitionData { + return FfiConverterTypePresentationDefinitionData.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_presentationdefinition_get_data( + it, _status) +} + } + ) + } + + + + @Throws(Web5RustCoreException::class)override fun `selectCredentials`(`vcJwts`: List): List { + return FfiConverterSequenceString.lift( + callWithPointer { + uniffiRustCallWithError(Web5RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_presentationdefinition_select_credentials( + it, FfiConverterSequenceString.lower(`vcJwts`),_status) +} + } + ) + } + + + + + + + companion object + +} + +public object FfiConverterTypePresentationDefinition: FfiConverter { + + override fun lower(value: PresentationDefinition): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): PresentationDefinition { + return PresentationDefinition(value) + } + + override fun read(buf: ByteBuffer): PresentationDefinition { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: PresentationDefinition) = 8UL + + override fun write(value: PresentationDefinition, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface QuoteInterface { + + fun `getData`(): QuoteData + + fun `toJson`(): kotlin.String + + companion object +} + +open class Quote: Disposable, AutoCloseable, QuoteInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`bearerDid`: BearerDid, `to`: kotlin.String, `from`: kotlin.String, `exchangeId`: kotlin.String, `data`: QuoteDataData, `protocol`: kotlin.String, `externalId`: kotlin.String?) : + this( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_quote_new( + FfiConverterTypeBearerDid.lower(`bearerDid`),FfiConverterString.lower(`to`),FfiConverterString.lower(`from`),FfiConverterString.lower(`exchangeId`),FfiConverterTypeQuoteDataData.lower(`data`),FfiConverterString.lower(`protocol`),FfiConverterOptionalString.lower(`externalId`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_quote(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_quote(pointer!!, status) + } + } + + + @Throws(RustCoreException::class)override fun `getData`(): QuoteData { + return FfiConverterTypeQuoteData.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_quote_get_data( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `toJson`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_quote_to_json( + it, _status) +} + } + ) + } + + + + + + companion object { + + @Throws(RustCoreException::class) fun `fromJsonString`(`json`: kotlin.String): Quote { + return FfiConverterTypeQuote.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_quote_from_json_string( + FfiConverterString.lower(`json`),_status) +} + ) + } + + + + } + +} + +public object FfiConverterTypeQuote: FfiConverter { + + override fun lower(value: Quote): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Quote { + return Quote(value) + } + + override fun read(buf: ByteBuffer): Quote { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Quote) = 8UL + + override fun write(value: Quote, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface RfqInterface { + + fun `getData`(): RfqData + + fun `toJson`(): kotlin.String + + fun `verifyAllPrivateData`(): kotlin.Boolean + + fun `verifyOfferingRequirements`(`offering`: Offering): kotlin.Boolean + + fun `verifyPresentPrivateData`(): kotlin.Boolean + + companion object +} + +open class Rfq: Disposable, AutoCloseable, RfqInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + constructor(`bearerDid`: BearerDid, `to`: kotlin.String, `from`: kotlin.String, `createRfqData`: CreateRfqDataData, `protocol`: kotlin.String, `externalId`: kotlin.String?) : + this( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_rfq_new( + FfiConverterTypeBearerDid.lower(`bearerDid`),FfiConverterString.lower(`to`),FfiConverterString.lower(`from`),FfiConverterTypeCreateRfqDataData.lower(`createRfqData`),FfiConverterString.lower(`protocol`),FfiConverterOptionalString.lower(`externalId`),_status) +} + ) + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_rfq(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_rfq(pointer!!, status) + } + } + + + @Throws(RustCoreException::class)override fun `getData`(): RfqData { + return FfiConverterTypeRfqData.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_rfq_get_data( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `toJson`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_rfq_to_json( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `verifyAllPrivateData`(): kotlin.Boolean { + return FfiConverterBoolean.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_rfq_verify_all_private_data( + it, _status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `verifyOfferingRequirements`(`offering`: Offering): kotlin.Boolean { + return FfiConverterBoolean.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_rfq_verify_offering_requirements( + it, FfiConverterTypeOffering.lower(`offering`),_status) +} + } + ) + } + + + + @Throws(RustCoreException::class)override fun `verifyPresentPrivateData`(): kotlin.Boolean { + return FfiConverterBoolean.lift( + callWithPointer { + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_rfq_verify_present_private_data( + it, _status) +} + } + ) + } + + + + + + companion object { + + @Throws(RustCoreException::class) fun `fromJsonString`(`json`: kotlin.String): Rfq { + return FfiConverterTypeRfq.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_constructor_rfq_from_json_string( + FfiConverterString.lower(`json`),_status) +} + ) + } + + + + } + +} + +public object FfiConverterTypeRfq: FfiConverter { + + override fun lower(value: Rfq): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Rfq { + return Rfq(value) + } + + override fun read(buf: ByteBuffer): Rfq { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Rfq) = 8UL + + override fun write(value: Rfq, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface RustCoreExceptionInterface { + + fun `errorType`(): kotlin.String + + fun `message`(): kotlin.String + + fun `variant`(): kotlin.String + + companion object +} + + +open class RustCoreException : Exception, Disposable, AutoCloseable, RustCoreExceptionInterface { + + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_rustcoreerror(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_rustcoreerror(pointer!!, status) + } + } + + override fun `errorType`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_rustcoreerror_error_type( + it, _status) +} + } + ) + } + + + override fun `message`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_rustcoreerror_message( + it, _status) +} + } + ) + } + + + override fun `variant`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_rustcoreerror_variant( + it, _status) +} + } + ) + } + + + + + + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): RustCoreException { + // Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer. + val bb = error_buf.asByteBuffer() + if (bb == null) { + throw InternalException("?") + } + return FfiConverterTypeRustCoreError.read(bb) + } + } + +} + +public object FfiConverterTypeRustCoreError: FfiConverter { + + override fun lower(value: RustCoreException): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): RustCoreException { + return RustCoreException(value) + } + + override fun read(buf: ByteBuffer): RustCoreException { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: RustCoreException) = 8UL + + override fun write(value: RustCoreException, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface SignerInterface { + + fun `sign`(`payload`: List): kotlin.ByteArray + + companion object +} + +open class Signer: Disposable, AutoCloseable, SignerInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_signer(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_signer(pointer!!, status) + } + } + + + @Throws(Web5RustCoreException::class)override fun `sign`(`payload`: List): kotlin.ByteArray { + return FfiConverterByteArray.lift( + callWithPointer { + uniffiRustCallWithError(Web5RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_signer_sign( + it, FfiConverterSequenceUByte.lower(`payload`),_status) +} + } + ) + } + + + + + + + companion object + +} + +public object FfiConverterTypeSigner: FfiConverter { + + override fun lower(value: Signer): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Signer { + return Signer(value) + } + + override fun read(buf: ByteBuffer): Signer { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Signer) = 8UL + + override fun write(value: Signer, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface Web5RustCoreExceptionInterface { + + fun `errorType`(): kotlin.String + + fun `message`(): kotlin.String + + fun `variant`(): kotlin.String + + companion object +} + + +open class Web5RustCoreException : Exception, Disposable, AutoCloseable, Web5RustCoreExceptionInterface { + + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_free_web5rustcoreerror(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_clone_web5rustcoreerror(pointer!!, status) + } + } + + override fun `errorType`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_web5rustcoreerror_error_type( + it, _status) +} + } + ) + } + + + override fun `message`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_web5rustcoreerror_message( + it, _status) +} + } + ) + } + + + override fun `variant`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_method_web5rustcoreerror_variant( + it, _status) +} + } + ) + } + + + + + + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): Web5RustCoreException { + // Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer. + val bb = error_buf.asByteBuffer() + if (bb == null) { + throw InternalException("?") + } + return FfiConverterTypeWeb5RustCoreError.read(bb) + } + } + +} + +public object FfiConverterTypeWeb5RustCoreError: FfiConverter { + + override fun lower(value: Web5RustCoreException): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Web5RustCoreException { + return Web5RustCoreException(value) + } + + override fun read(buf: ByteBuffer): Web5RustCoreException { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Web5RustCoreException) = 8UL + + override fun write(value: Web5RustCoreException, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + + +data class BalanceData ( + var `metadata`: ResourceMetadataData, + var `data`: BalanceDataData, + var `signature`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeBalanceData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BalanceData { + return BalanceData( + FfiConverterTypeResourceMetadataData.read(buf), + FfiConverterTypeBalanceDataData.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: BalanceData) = ( + FfiConverterTypeResourceMetadataData.allocationSize(value.`metadata`) + + FfiConverterTypeBalanceDataData.allocationSize(value.`data`) + + FfiConverterString.allocationSize(value.`signature`) + ) + + override fun write(value: BalanceData, buf: ByteBuffer) { + FfiConverterTypeResourceMetadataData.write(value.`metadata`, buf) + FfiConverterTypeBalanceDataData.write(value.`data`, buf) + FfiConverterString.write(value.`signature`, buf) + } +} + + + +data class BalanceDataData ( + var `currencyCode`: kotlin.String, + var `available`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeBalanceDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BalanceDataData { + return BalanceDataData( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: BalanceDataData) = ( + FfiConverterString.allocationSize(value.`currencyCode`) + + FfiConverterString.allocationSize(value.`available`) + ) + + override fun write(value: BalanceDataData, buf: ByteBuffer) { + FfiConverterString.write(value.`currencyCode`, buf) + FfiConverterString.write(value.`available`, buf) + } +} + + + +data class BearerDidData ( + var `did`: DidData, + var `document`: DocumentData, + var `keyManager`: KeyManager +) : Disposable { + + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + + Disposable.destroy( + this.`did`, + this.`document`, + this.`keyManager`) + } + + companion object +} + +public object FfiConverterTypeBearerDidData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BearerDidData { + return BearerDidData( + FfiConverterTypeDidData.read(buf), + FfiConverterTypeDocumentData.read(buf), + FfiConverterTypeKeyManager.read(buf), + ) + } + + override fun allocationSize(value: BearerDidData) = ( + FfiConverterTypeDidData.allocationSize(value.`did`) + + FfiConverterTypeDocumentData.allocationSize(value.`document`) + + FfiConverterTypeKeyManager.allocationSize(value.`keyManager`) + ) + + override fun write(value: BearerDidData, buf: ByteBuffer) { + FfiConverterTypeDidData.write(value.`did`, buf) + FfiConverterTypeDocumentData.write(value.`document`, buf) + FfiConverterTypeKeyManager.write(value.`keyManager`, buf) + } +} + + + +data class CloseData ( + var `metadata`: MessageMetadataData, + var `data`: CloseDataData, + var `signature`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeCloseData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CloseData { + return CloseData( + FfiConverterTypeMessageMetadataData.read(buf), + FfiConverterTypeCloseDataData.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: CloseData) = ( + FfiConverterTypeMessageMetadataData.allocationSize(value.`metadata`) + + FfiConverterTypeCloseDataData.allocationSize(value.`data`) + + FfiConverterString.allocationSize(value.`signature`) + ) + + override fun write(value: CloseData, buf: ByteBuffer) { + FfiConverterTypeMessageMetadataData.write(value.`metadata`, buf) + FfiConverterTypeCloseDataData.write(value.`data`, buf) + FfiConverterString.write(value.`signature`, buf) + } +} + + + +data class CloseDataData ( + var `reason`: kotlin.String?, + var `success`: kotlin.Boolean? +) { + + companion object +} + +public object FfiConverterTypeCloseDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CloseDataData { + return CloseDataData( + FfiConverterOptionalString.read(buf), + FfiConverterOptionalBoolean.read(buf), + ) + } + + override fun allocationSize(value: CloseDataData) = ( + FfiConverterOptionalString.allocationSize(value.`reason`) + + FfiConverterOptionalBoolean.allocationSize(value.`success`) + ) + + override fun write(value: CloseDataData, buf: ByteBuffer) { + FfiConverterOptionalString.write(value.`reason`, buf) + FfiConverterOptionalBoolean.write(value.`success`, buf) + } +} + + + +data class ConstraintsData ( + var `fields`: List +) { + + companion object +} + +public object FfiConverterTypeConstraintsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ConstraintsData { + return ConstraintsData( + FfiConverterSequenceTypeFieldData.read(buf), + ) + } + + override fun allocationSize(value: ConstraintsData) = ( + FfiConverterSequenceTypeFieldData.allocationSize(value.`fields`) + ) + + override fun write(value: ConstraintsData, buf: ByteBuffer) { + FfiConverterSequenceTypeFieldData.write(value.`fields`, buf) + } +} + + + +data class CreateRfqDataData ( + var `offeringId`: kotlin.String, + var `payin`: CreateSelectedPayinMethodData, + var `payout`: CreateSelectedPayoutMethodData, + var `claims`: List +) { + + companion object +} + +public object FfiConverterTypeCreateRfqDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CreateRfqDataData { + return CreateRfqDataData( + FfiConverterString.read(buf), + FfiConverterTypeCreateSelectedPayinMethodData.read(buf), + FfiConverterTypeCreateSelectedPayoutMethodData.read(buf), + FfiConverterSequenceString.read(buf), + ) + } + + override fun allocationSize(value: CreateRfqDataData) = ( + FfiConverterString.allocationSize(value.`offeringId`) + + FfiConverterTypeCreateSelectedPayinMethodData.allocationSize(value.`payin`) + + FfiConverterTypeCreateSelectedPayoutMethodData.allocationSize(value.`payout`) + + FfiConverterSequenceString.allocationSize(value.`claims`) + ) + + override fun write(value: CreateRfqDataData, buf: ByteBuffer) { + FfiConverterString.write(value.`offeringId`, buf) + FfiConverterTypeCreateSelectedPayinMethodData.write(value.`payin`, buf) + FfiConverterTypeCreateSelectedPayoutMethodData.write(value.`payout`, buf) + FfiConverterSequenceString.write(value.`claims`, buf) + } +} + + + +data class CreateSelectedPayinMethodData ( + var `kind`: kotlin.String, + var `paymentDetails`: kotlin.String?, + var `amount`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeCreateSelectedPayinMethodData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CreateSelectedPayinMethodData { + return CreateSelectedPayinMethodData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: CreateSelectedPayinMethodData) = ( + FfiConverterString.allocationSize(value.`kind`) + + FfiConverterOptionalString.allocationSize(value.`paymentDetails`) + + FfiConverterString.allocationSize(value.`amount`) + ) + + override fun write(value: CreateSelectedPayinMethodData, buf: ByteBuffer) { + FfiConverterString.write(value.`kind`, buf) + FfiConverterOptionalString.write(value.`paymentDetails`, buf) + FfiConverterString.write(value.`amount`, buf) + } +} + + + +data class CreateSelectedPayoutMethodData ( + var `kind`: kotlin.String, + var `paymentDetails`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypeCreateSelectedPayoutMethodData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CreateSelectedPayoutMethodData { + return CreateSelectedPayoutMethodData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: CreateSelectedPayoutMethodData) = ( + FfiConverterString.allocationSize(value.`kind`) + + FfiConverterOptionalString.allocationSize(value.`paymentDetails`) + ) + + override fun write(value: CreateSelectedPayoutMethodData, buf: ByteBuffer) { + FfiConverterString.write(value.`kind`, buf) + FfiConverterOptionalString.write(value.`paymentDetails`, buf) + } +} + + + +data class DidData ( + var `uri`: kotlin.String, + var `url`: kotlin.String, + var `method`: kotlin.String, + var `id`: kotlin.String, + var `params`: Map?, + var `path`: kotlin.String?, + var `query`: kotlin.String?, + var `fragment`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypeDidData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DidData { + return DidData( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalMapStringString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: DidData) = ( + FfiConverterString.allocationSize(value.`uri`) + + FfiConverterString.allocationSize(value.`url`) + + FfiConverterString.allocationSize(value.`method`) + + FfiConverterString.allocationSize(value.`id`) + + FfiConverterOptionalMapStringString.allocationSize(value.`params`) + + FfiConverterOptionalString.allocationSize(value.`path`) + + FfiConverterOptionalString.allocationSize(value.`query`) + + FfiConverterOptionalString.allocationSize(value.`fragment`) + ) + + override fun write(value: DidData, buf: ByteBuffer) { + FfiConverterString.write(value.`uri`, buf) + FfiConverterString.write(value.`url`, buf) + FfiConverterString.write(value.`method`, buf) + FfiConverterString.write(value.`id`, buf) + FfiConverterOptionalMapStringString.write(value.`params`, buf) + FfiConverterOptionalString.write(value.`path`, buf) + FfiConverterOptionalString.write(value.`query`, buf) + FfiConverterOptionalString.write(value.`fragment`, buf) + } +} + + + +data class DocumentData ( + var `id`: kotlin.String, + var `context`: List?, + var `controller`: List?, + var `alsoKnownAs`: List?, + var `verificationMethod`: List, + var `authentication`: List?, + var `assertionMethod`: List?, + var `keyAgreement`: List?, + var `capabilityInvocation`: List?, + var `capabilityDelegation`: List?, + var `service`: List? +) { + + companion object +} + +public object FfiConverterTypeDocumentData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DocumentData { + return DocumentData( + FfiConverterString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterSequenceTypeVerificationMethodData.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalSequenceTypeServiceData.read(buf), + ) + } + + override fun allocationSize(value: DocumentData) = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterOptionalSequenceString.allocationSize(value.`context`) + + FfiConverterOptionalSequenceString.allocationSize(value.`controller`) + + FfiConverterOptionalSequenceString.allocationSize(value.`alsoKnownAs`) + + FfiConverterSequenceTypeVerificationMethodData.allocationSize(value.`verificationMethod`) + + FfiConverterOptionalSequenceString.allocationSize(value.`authentication`) + + FfiConverterOptionalSequenceString.allocationSize(value.`assertionMethod`) + + FfiConverterOptionalSequenceString.allocationSize(value.`keyAgreement`) + + FfiConverterOptionalSequenceString.allocationSize(value.`capabilityInvocation`) + + FfiConverterOptionalSequenceString.allocationSize(value.`capabilityDelegation`) + + FfiConverterOptionalSequenceTypeServiceData.allocationSize(value.`service`) + ) + + override fun write(value: DocumentData, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterOptionalSequenceString.write(value.`context`, buf) + FfiConverterOptionalSequenceString.write(value.`controller`, buf) + FfiConverterOptionalSequenceString.write(value.`alsoKnownAs`, buf) + FfiConverterSequenceTypeVerificationMethodData.write(value.`verificationMethod`, buf) + FfiConverterOptionalSequenceString.write(value.`authentication`, buf) + FfiConverterOptionalSequenceString.write(value.`assertionMethod`, buf) + FfiConverterOptionalSequenceString.write(value.`keyAgreement`, buf) + FfiConverterOptionalSequenceString.write(value.`capabilityInvocation`, buf) + FfiConverterOptionalSequenceString.write(value.`capabilityDelegation`, buf) + FfiConverterOptionalSequenceTypeServiceData.write(value.`service`, buf) + } +} + + + +data class ExchangeData ( + var `rfq`: Rfq, + var `quote`: Quote?, + var `order`: Order?, + var `orderStatuses`: List?, + var `close`: Close? +) : Disposable { + + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + + Disposable.destroy( + this.`rfq`, + this.`quote`, + this.`order`, + this.`orderStatuses`, + this.`close`) + } + + companion object +} + +public object FfiConverterTypeExchangeData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ExchangeData { + return ExchangeData( + FfiConverterTypeRfq.read(buf), + FfiConverterOptionalTypeQuote.read(buf), + FfiConverterOptionalTypeOrder.read(buf), + FfiConverterOptionalSequenceTypeOrderStatus.read(buf), + FfiConverterOptionalTypeClose.read(buf), + ) + } + + override fun allocationSize(value: ExchangeData) = ( + FfiConverterTypeRfq.allocationSize(value.`rfq`) + + FfiConverterOptionalTypeQuote.allocationSize(value.`quote`) + + FfiConverterOptionalTypeOrder.allocationSize(value.`order`) + + FfiConverterOptionalSequenceTypeOrderStatus.allocationSize(value.`orderStatuses`) + + FfiConverterOptionalTypeClose.allocationSize(value.`close`) + ) + + override fun write(value: ExchangeData, buf: ByteBuffer) { + FfiConverterTypeRfq.write(value.`rfq`, buf) + FfiConverterOptionalTypeQuote.write(value.`quote`, buf) + FfiConverterOptionalTypeOrder.write(value.`order`, buf) + FfiConverterOptionalSequenceTypeOrderStatus.write(value.`orderStatuses`, buf) + FfiConverterOptionalTypeClose.write(value.`close`, buf) + } +} + + + +data class FieldData ( + var `id`: kotlin.String?, + var `name`: kotlin.String?, + var `path`: List, + var `purpose`: kotlin.String?, + var `filter`: FilterData?, + var `optional`: kotlin.Boolean?, + var `predicate`: Optionality? +) { + + companion object +} + +public object FfiConverterTypeFieldData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FieldData { + return FieldData( + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterSequenceString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalTypeFilterData.read(buf), + FfiConverterOptionalBoolean.read(buf), + FfiConverterOptionalTypeOptionality.read(buf), + ) + } + + override fun allocationSize(value: FieldData) = ( + FfiConverterOptionalString.allocationSize(value.`id`) + + FfiConverterOptionalString.allocationSize(value.`name`) + + FfiConverterSequenceString.allocationSize(value.`path`) + + FfiConverterOptionalString.allocationSize(value.`purpose`) + + FfiConverterOptionalTypeFilterData.allocationSize(value.`filter`) + + FfiConverterOptionalBoolean.allocationSize(value.`optional`) + + FfiConverterOptionalTypeOptionality.allocationSize(value.`predicate`) + ) + + override fun write(value: FieldData, buf: ByteBuffer) { + FfiConverterOptionalString.write(value.`id`, buf) + FfiConverterOptionalString.write(value.`name`, buf) + FfiConverterSequenceString.write(value.`path`, buf) + FfiConverterOptionalString.write(value.`purpose`, buf) + FfiConverterOptionalTypeFilterData.write(value.`filter`, buf) + FfiConverterOptionalBoolean.write(value.`optional`, buf) + FfiConverterOptionalTypeOptionality.write(value.`predicate`, buf) + } +} + + + +data class FilterData ( + var `type`: kotlin.String?, + var `pattern`: kotlin.String?, + var `constValue`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypeFilterData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FilterData { + return FilterData( + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: FilterData) = ( + FfiConverterOptionalString.allocationSize(value.`type`) + + FfiConverterOptionalString.allocationSize(value.`pattern`) + + FfiConverterOptionalString.allocationSize(value.`constValue`) + ) + + override fun write(value: FilterData, buf: ByteBuffer) { + FfiConverterOptionalString.write(value.`type`, buf) + FfiConverterOptionalString.write(value.`pattern`, buf) + FfiConverterOptionalString.write(value.`constValue`, buf) + } +} + + + +data class InputDescriptorData ( + var `id`: kotlin.String, + var `name`: kotlin.String?, + var `purpose`: kotlin.String?, + var `constraints`: ConstraintsData +) { + + companion object +} + +public object FfiConverterTypeInputDescriptorData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): InputDescriptorData { + return InputDescriptorData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterTypeConstraintsData.read(buf), + ) + } + + override fun allocationSize(value: InputDescriptorData) = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterOptionalString.allocationSize(value.`name`) + + FfiConverterOptionalString.allocationSize(value.`purpose`) + + FfiConverterTypeConstraintsData.allocationSize(value.`constraints`) + ) + + override fun write(value: InputDescriptorData, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterOptionalString.write(value.`name`, buf) + FfiConverterOptionalString.write(value.`purpose`, buf) + FfiConverterTypeConstraintsData.write(value.`constraints`, buf) + } +} + + + +data class JwkData ( + var `alg`: kotlin.String, + var `kty`: kotlin.String, + var `crv`: kotlin.String, + var `d`: kotlin.String?, + var `x`: kotlin.String, + var `y`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypeJwkData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): JwkData { + return JwkData( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: JwkData) = ( + FfiConverterString.allocationSize(value.`alg`) + + FfiConverterString.allocationSize(value.`kty`) + + FfiConverterString.allocationSize(value.`crv`) + + FfiConverterOptionalString.allocationSize(value.`d`) + + FfiConverterString.allocationSize(value.`x`) + + FfiConverterOptionalString.allocationSize(value.`y`) + ) + + override fun write(value: JwkData, buf: ByteBuffer) { + FfiConverterString.write(value.`alg`, buf) + FfiConverterString.write(value.`kty`, buf) + FfiConverterString.write(value.`crv`, buf) + FfiConverterOptionalString.write(value.`d`, buf) + FfiConverterString.write(value.`x`, buf) + FfiConverterOptionalString.write(value.`y`, buf) + } +} + + + +data class MessageMetadataData ( + var `from`: kotlin.String, + var `to`: kotlin.String, + var `kind`: MessageKind, + var `id`: kotlin.String, + var `exchangeId`: kotlin.String, + var `externalId`: kotlin.String?, + var `protocol`: kotlin.String, + var `createdAt`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeMessageMetadataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): MessageMetadataData { + return MessageMetadataData( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterTypeMessageKind.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: MessageMetadataData) = ( + FfiConverterString.allocationSize(value.`from`) + + FfiConverterString.allocationSize(value.`to`) + + FfiConverterTypeMessageKind.allocationSize(value.`kind`) + + FfiConverterString.allocationSize(value.`id`) + + FfiConverterString.allocationSize(value.`exchangeId`) + + FfiConverterOptionalString.allocationSize(value.`externalId`) + + FfiConverterString.allocationSize(value.`protocol`) + + FfiConverterString.allocationSize(value.`createdAt`) + ) + + override fun write(value: MessageMetadataData, buf: ByteBuffer) { + FfiConverterString.write(value.`from`, buf) + FfiConverterString.write(value.`to`, buf) + FfiConverterTypeMessageKind.write(value.`kind`, buf) + FfiConverterString.write(value.`id`, buf) + FfiConverterString.write(value.`exchangeId`, buf) + FfiConverterOptionalString.write(value.`externalId`, buf) + FfiConverterString.write(value.`protocol`, buf) + FfiConverterString.write(value.`createdAt`, buf) + } +} + + + +data class OfferingData ( + var `metadata`: ResourceMetadataData, + var `data`: OfferingDataData, + var `signature`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeOfferingData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): OfferingData { + return OfferingData( + FfiConverterTypeResourceMetadataData.read(buf), + FfiConverterTypeOfferingDataData.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: OfferingData) = ( + FfiConverterTypeResourceMetadataData.allocationSize(value.`metadata`) + + FfiConverterTypeOfferingDataData.allocationSize(value.`data`) + + FfiConverterString.allocationSize(value.`signature`) + ) + + override fun write(value: OfferingData, buf: ByteBuffer) { + FfiConverterTypeResourceMetadataData.write(value.`metadata`, buf) + FfiConverterTypeOfferingDataData.write(value.`data`, buf) + FfiConverterString.write(value.`signature`, buf) + } +} + + + +data class OfferingDataData ( + var `description`: kotlin.String, + var `payoutUnitsPerPayinUnit`: kotlin.String, + var `payin`: PayinDetailsData, + var `payout`: PayoutDetailsData, + var `requiredClaims`: PresentationDefinitionData? +) { + + companion object +} + +public object FfiConverterTypeOfferingDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): OfferingDataData { + return OfferingDataData( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterTypePayinDetailsData.read(buf), + FfiConverterTypePayoutDetailsData.read(buf), + FfiConverterOptionalTypePresentationDefinitionData.read(buf), + ) + } + + override fun allocationSize(value: OfferingDataData) = ( + FfiConverterString.allocationSize(value.`description`) + + FfiConverterString.allocationSize(value.`payoutUnitsPerPayinUnit`) + + FfiConverterTypePayinDetailsData.allocationSize(value.`payin`) + + FfiConverterTypePayoutDetailsData.allocationSize(value.`payout`) + + FfiConverterOptionalTypePresentationDefinitionData.allocationSize(value.`requiredClaims`) + ) + + override fun write(value: OfferingDataData, buf: ByteBuffer) { + FfiConverterString.write(value.`description`, buf) + FfiConverterString.write(value.`payoutUnitsPerPayinUnit`, buf) + FfiConverterTypePayinDetailsData.write(value.`payin`, buf) + FfiConverterTypePayoutDetailsData.write(value.`payout`, buf) + FfiConverterOptionalTypePresentationDefinitionData.write(value.`requiredClaims`, buf) + } +} + + + +data class OrderData ( + var `metadata`: MessageMetadataData, + var `data`: OrderDataData, + var `signature`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeOrderData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): OrderData { + return OrderData( + FfiConverterTypeMessageMetadataData.read(buf), + FfiConverterTypeOrderDataData.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: OrderData) = ( + FfiConverterTypeMessageMetadataData.allocationSize(value.`metadata`) + + FfiConverterTypeOrderDataData.allocationSize(value.`data`) + + FfiConverterString.allocationSize(value.`signature`) + ) + + override fun write(value: OrderData, buf: ByteBuffer) { + FfiConverterTypeMessageMetadataData.write(value.`metadata`, buf) + FfiConverterTypeOrderDataData.write(value.`data`, buf) + FfiConverterString.write(value.`signature`, buf) + } +} + + + +class OrderDataData { + override fun equals(other: Any?): Boolean { + return other is OrderDataData + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + companion object +} + +public object FfiConverterTypeOrderDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): OrderDataData { + return OrderDataData() + } + + override fun allocationSize(value: OrderDataData) = 0UL + + override fun write(value: OrderDataData, buf: ByteBuffer) { + } +} + + + +data class OrderStatusData ( + var `metadata`: MessageMetadataData, + var `data`: OrderStatusDataData, + var `signature`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeOrderStatusData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): OrderStatusData { + return OrderStatusData( + FfiConverterTypeMessageMetadataData.read(buf), + FfiConverterTypeOrderStatusDataData.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: OrderStatusData) = ( + FfiConverterTypeMessageMetadataData.allocationSize(value.`metadata`) + + FfiConverterTypeOrderStatusDataData.allocationSize(value.`data`) + + FfiConverterString.allocationSize(value.`signature`) + ) + + override fun write(value: OrderStatusData, buf: ByteBuffer) { + FfiConverterTypeMessageMetadataData.write(value.`metadata`, buf) + FfiConverterTypeOrderStatusDataData.write(value.`data`, buf) + FfiConverterString.write(value.`signature`, buf) + } +} + + + +data class OrderStatusDataData ( + var `orderStatus`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeOrderStatusDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): OrderStatusDataData { + return OrderStatusDataData( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: OrderStatusDataData) = ( + FfiConverterString.allocationSize(value.`orderStatus`) + ) + + override fun write(value: OrderStatusDataData, buf: ByteBuffer) { + FfiConverterString.write(value.`orderStatus`, buf) + } +} + + + +data class PayinDetailsData ( + var `currencyCode`: kotlin.String, + var `min`: kotlin.String?, + var `max`: kotlin.String?, + var `methods`: List +) { + + companion object +} + +public object FfiConverterTypePayinDetailsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PayinDetailsData { + return PayinDetailsData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterSequenceTypePayinMethodData.read(buf), + ) + } + + override fun allocationSize(value: PayinDetailsData) = ( + FfiConverterString.allocationSize(value.`currencyCode`) + + FfiConverterOptionalString.allocationSize(value.`min`) + + FfiConverterOptionalString.allocationSize(value.`max`) + + FfiConverterSequenceTypePayinMethodData.allocationSize(value.`methods`) + ) + + override fun write(value: PayinDetailsData, buf: ByteBuffer) { + FfiConverterString.write(value.`currencyCode`, buf) + FfiConverterOptionalString.write(value.`min`, buf) + FfiConverterOptionalString.write(value.`max`, buf) + FfiConverterSequenceTypePayinMethodData.write(value.`methods`, buf) + } +} + + + +data class PayinMethodData ( + var `kind`: kotlin.String, + var `name`: kotlin.String?, + var `description`: kotlin.String?, + var `group`: kotlin.String?, + var `requiredPaymentDetails`: kotlin.String?, + var `fee`: kotlin.String?, + var `min`: kotlin.String?, + var `max`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypePayinMethodData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PayinMethodData { + return PayinMethodData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: PayinMethodData) = ( + FfiConverterString.allocationSize(value.`kind`) + + FfiConverterOptionalString.allocationSize(value.`name`) + + FfiConverterOptionalString.allocationSize(value.`description`) + + FfiConverterOptionalString.allocationSize(value.`group`) + + FfiConverterOptionalString.allocationSize(value.`requiredPaymentDetails`) + + FfiConverterOptionalString.allocationSize(value.`fee`) + + FfiConverterOptionalString.allocationSize(value.`min`) + + FfiConverterOptionalString.allocationSize(value.`max`) + ) + + override fun write(value: PayinMethodData, buf: ByteBuffer) { + FfiConverterString.write(value.`kind`, buf) + FfiConverterOptionalString.write(value.`name`, buf) + FfiConverterOptionalString.write(value.`description`, buf) + FfiConverterOptionalString.write(value.`group`, buf) + FfiConverterOptionalString.write(value.`requiredPaymentDetails`, buf) + FfiConverterOptionalString.write(value.`fee`, buf) + FfiConverterOptionalString.write(value.`min`, buf) + FfiConverterOptionalString.write(value.`max`, buf) + } +} + + + +data class PaymentInstructionsData ( + var `link`: kotlin.String?, + var `instruction`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypePaymentInstructionsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PaymentInstructionsData { + return PaymentInstructionsData( + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: PaymentInstructionsData) = ( + FfiConverterOptionalString.allocationSize(value.`link`) + + FfiConverterOptionalString.allocationSize(value.`instruction`) + ) + + override fun write(value: PaymentInstructionsData, buf: ByteBuffer) { + FfiConverterOptionalString.write(value.`link`, buf) + FfiConverterOptionalString.write(value.`instruction`, buf) + } +} + + + +data class PayoutDetailsData ( + var `currencyCode`: kotlin.String, + var `min`: kotlin.String?, + var `max`: kotlin.String?, + var `methods`: List +) { + + companion object +} + +public object FfiConverterTypePayoutDetailsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PayoutDetailsData { + return PayoutDetailsData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterSequenceTypePayoutMethodData.read(buf), + ) + } + + override fun allocationSize(value: PayoutDetailsData) = ( + FfiConverterString.allocationSize(value.`currencyCode`) + + FfiConverterOptionalString.allocationSize(value.`min`) + + FfiConverterOptionalString.allocationSize(value.`max`) + + FfiConverterSequenceTypePayoutMethodData.allocationSize(value.`methods`) + ) + + override fun write(value: PayoutDetailsData, buf: ByteBuffer) { + FfiConverterString.write(value.`currencyCode`, buf) + FfiConverterOptionalString.write(value.`min`, buf) + FfiConverterOptionalString.write(value.`max`, buf) + FfiConverterSequenceTypePayoutMethodData.write(value.`methods`, buf) + } +} + + + +data class PayoutMethodData ( + var `kind`: kotlin.String, + var `name`: kotlin.String?, + var `description`: kotlin.String?, + var `group`: kotlin.String?, + var `requiredPaymentDetails`: kotlin.String?, + var `fee`: kotlin.String?, + var `min`: kotlin.String?, + var `max`: kotlin.String?, + var `estimatedSettlementTime`: kotlin.Long +) { + + companion object +} + +public object FfiConverterTypePayoutMethodData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PayoutMethodData { + return PayoutMethodData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterLong.read(buf), + ) + } + + override fun allocationSize(value: PayoutMethodData) = ( + FfiConverterString.allocationSize(value.`kind`) + + FfiConverterOptionalString.allocationSize(value.`name`) + + FfiConverterOptionalString.allocationSize(value.`description`) + + FfiConverterOptionalString.allocationSize(value.`group`) + + FfiConverterOptionalString.allocationSize(value.`requiredPaymentDetails`) + + FfiConverterOptionalString.allocationSize(value.`fee`) + + FfiConverterOptionalString.allocationSize(value.`min`) + + FfiConverterOptionalString.allocationSize(value.`max`) + + FfiConverterLong.allocationSize(value.`estimatedSettlementTime`) + ) + + override fun write(value: PayoutMethodData, buf: ByteBuffer) { + FfiConverterString.write(value.`kind`, buf) + FfiConverterOptionalString.write(value.`name`, buf) + FfiConverterOptionalString.write(value.`description`, buf) + FfiConverterOptionalString.write(value.`group`, buf) + FfiConverterOptionalString.write(value.`requiredPaymentDetails`, buf) + FfiConverterOptionalString.write(value.`fee`, buf) + FfiConverterOptionalString.write(value.`min`, buf) + FfiConverterOptionalString.write(value.`max`, buf) + FfiConverterLong.write(value.`estimatedSettlementTime`, buf) + } +} + + + +data class PresentationDefinitionData ( + var `id`: kotlin.String, + var `name`: kotlin.String?, + var `purpose`: kotlin.String?, + var `inputDescriptors`: List +) { + + companion object +} + +public object FfiConverterTypePresentationDefinitionData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PresentationDefinitionData { + return PresentationDefinitionData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterSequenceTypeInputDescriptorData.read(buf), + ) + } + + override fun allocationSize(value: PresentationDefinitionData) = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterOptionalString.allocationSize(value.`name`) + + FfiConverterOptionalString.allocationSize(value.`purpose`) + + FfiConverterSequenceTypeInputDescriptorData.allocationSize(value.`inputDescriptors`) + ) + + override fun write(value: PresentationDefinitionData, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterOptionalString.write(value.`name`, buf) + FfiConverterOptionalString.write(value.`purpose`, buf) + FfiConverterSequenceTypeInputDescriptorData.write(value.`inputDescriptors`, buf) + } +} + + + +data class PrivatePaymentDetailsData ( + var `paymentDetails`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypePrivatePaymentDetailsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PrivatePaymentDetailsData { + return PrivatePaymentDetailsData( + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: PrivatePaymentDetailsData) = ( + FfiConverterOptionalString.allocationSize(value.`paymentDetails`) + ) + + override fun write(value: PrivatePaymentDetailsData, buf: ByteBuffer) { + FfiConverterOptionalString.write(value.`paymentDetails`, buf) + } +} + + + +data class QuoteData ( + var `metadata`: MessageMetadataData, + var `data`: QuoteDataData, + var `signature`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeQuoteData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): QuoteData { + return QuoteData( + FfiConverterTypeMessageMetadataData.read(buf), + FfiConverterTypeQuoteDataData.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: QuoteData) = ( + FfiConverterTypeMessageMetadataData.allocationSize(value.`metadata`) + + FfiConverterTypeQuoteDataData.allocationSize(value.`data`) + + FfiConverterString.allocationSize(value.`signature`) + ) + + override fun write(value: QuoteData, buf: ByteBuffer) { + FfiConverterTypeMessageMetadataData.write(value.`metadata`, buf) + FfiConverterTypeQuoteDataData.write(value.`data`, buf) + FfiConverterString.write(value.`signature`, buf) + } +} + + + +data class QuoteDataData ( + var `expiresAt`: kotlin.String, + var `payin`: QuoteDetailsData, + var `payout`: QuoteDetailsData +) { + + companion object +} + +public object FfiConverterTypeQuoteDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): QuoteDataData { + return QuoteDataData( + FfiConverterString.read(buf), + FfiConverterTypeQuoteDetailsData.read(buf), + FfiConverterTypeQuoteDetailsData.read(buf), + ) + } + + override fun allocationSize(value: QuoteDataData) = ( + FfiConverterString.allocationSize(value.`expiresAt`) + + FfiConverterTypeQuoteDetailsData.allocationSize(value.`payin`) + + FfiConverterTypeQuoteDetailsData.allocationSize(value.`payout`) + ) + + override fun write(value: QuoteDataData, buf: ByteBuffer) { + FfiConverterString.write(value.`expiresAt`, buf) + FfiConverterTypeQuoteDetailsData.write(value.`payin`, buf) + FfiConverterTypeQuoteDetailsData.write(value.`payout`, buf) + } +} + + + +data class QuoteDetailsData ( + var `currencyCode`: kotlin.String, + var `amount`: kotlin.String, + var `fee`: kotlin.String?, + var `paymentInstructions`: PaymentInstructionsData? +) { + + companion object +} + +public object FfiConverterTypeQuoteDetailsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): QuoteDetailsData { + return QuoteDetailsData( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalTypePaymentInstructionsData.read(buf), + ) + } + + override fun allocationSize(value: QuoteDetailsData) = ( + FfiConverterString.allocationSize(value.`currencyCode`) + + FfiConverterString.allocationSize(value.`amount`) + + FfiConverterOptionalString.allocationSize(value.`fee`) + + FfiConverterOptionalTypePaymentInstructionsData.allocationSize(value.`paymentInstructions`) + ) + + override fun write(value: QuoteDetailsData, buf: ByteBuffer) { + FfiConverterString.write(value.`currencyCode`, buf) + FfiConverterString.write(value.`amount`, buf) + FfiConverterOptionalString.write(value.`fee`, buf) + FfiConverterOptionalTypePaymentInstructionsData.write(value.`paymentInstructions`, buf) + } +} + + + +data class ResourceMetadataData ( + var `kind`: ResourceKind, + var `from`: kotlin.String, + var `id`: kotlin.String, + var `protocol`: kotlin.String, + var `createdAt`: kotlin.String, + var `updatedAt`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypeResourceMetadataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ResourceMetadataData { + return ResourceMetadataData( + FfiConverterTypeResourceKind.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: ResourceMetadataData) = ( + FfiConverterTypeResourceKind.allocationSize(value.`kind`) + + FfiConverterString.allocationSize(value.`from`) + + FfiConverterString.allocationSize(value.`id`) + + FfiConverterString.allocationSize(value.`protocol`) + + FfiConverterString.allocationSize(value.`createdAt`) + + FfiConverterOptionalString.allocationSize(value.`updatedAt`) + ) + + override fun write(value: ResourceMetadataData, buf: ByteBuffer) { + FfiConverterTypeResourceKind.write(value.`kind`, buf) + FfiConverterString.write(value.`from`, buf) + FfiConverterString.write(value.`id`, buf) + FfiConverterString.write(value.`protocol`, buf) + FfiConverterString.write(value.`createdAt`, buf) + FfiConverterOptionalString.write(value.`updatedAt`, buf) + } +} + + + +data class RfqData ( + var `metadata`: MessageMetadataData, + var `data`: RfqDataData, + var `privateData`: RfqPrivateDataData, + var `signature`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeRfqData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): RfqData { + return RfqData( + FfiConverterTypeMessageMetadataData.read(buf), + FfiConverterTypeRfqDataData.read(buf), + FfiConverterTypeRfqPrivateDataData.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: RfqData) = ( + FfiConverterTypeMessageMetadataData.allocationSize(value.`metadata`) + + FfiConverterTypeRfqDataData.allocationSize(value.`data`) + + FfiConverterTypeRfqPrivateDataData.allocationSize(value.`privateData`) + + FfiConverterString.allocationSize(value.`signature`) + ) + + override fun write(value: RfqData, buf: ByteBuffer) { + FfiConverterTypeMessageMetadataData.write(value.`metadata`, buf) + FfiConverterTypeRfqDataData.write(value.`data`, buf) + FfiConverterTypeRfqPrivateDataData.write(value.`privateData`, buf) + FfiConverterString.write(value.`signature`, buf) + } +} + + + +data class RfqDataData ( + var `offeringId`: kotlin.String, + var `payin`: SelectedPayinMethodData, + var `payout`: SelectedPayoutMethodData, + var `claimsHash`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypeRfqDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): RfqDataData { + return RfqDataData( + FfiConverterString.read(buf), + FfiConverterTypeSelectedPayinMethodData.read(buf), + FfiConverterTypeSelectedPayoutMethodData.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: RfqDataData) = ( + FfiConverterString.allocationSize(value.`offeringId`) + + FfiConverterTypeSelectedPayinMethodData.allocationSize(value.`payin`) + + FfiConverterTypeSelectedPayoutMethodData.allocationSize(value.`payout`) + + FfiConverterOptionalString.allocationSize(value.`claimsHash`) + ) + + override fun write(value: RfqDataData, buf: ByteBuffer) { + FfiConverterString.write(value.`offeringId`, buf) + FfiConverterTypeSelectedPayinMethodData.write(value.`payin`, buf) + FfiConverterTypeSelectedPayoutMethodData.write(value.`payout`, buf) + FfiConverterOptionalString.write(value.`claimsHash`, buf) + } +} + + + +data class RfqPrivateDataData ( + var `salt`: kotlin.String, + var `payin`: PrivatePaymentDetailsData?, + var `payout`: PrivatePaymentDetailsData?, + var `claims`: List? +) { + + companion object +} + +public object FfiConverterTypeRfqPrivateDataData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): RfqPrivateDataData { + return RfqPrivateDataData( + FfiConverterString.read(buf), + FfiConverterOptionalTypePrivatePaymentDetailsData.read(buf), + FfiConverterOptionalTypePrivatePaymentDetailsData.read(buf), + FfiConverterOptionalSequenceString.read(buf), + ) + } + + override fun allocationSize(value: RfqPrivateDataData) = ( + FfiConverterString.allocationSize(value.`salt`) + + FfiConverterOptionalTypePrivatePaymentDetailsData.allocationSize(value.`payin`) + + FfiConverterOptionalTypePrivatePaymentDetailsData.allocationSize(value.`payout`) + + FfiConverterOptionalSequenceString.allocationSize(value.`claims`) + ) + + override fun write(value: RfqPrivateDataData, buf: ByteBuffer) { + FfiConverterString.write(value.`salt`, buf) + FfiConverterOptionalTypePrivatePaymentDetailsData.write(value.`payin`, buf) + FfiConverterOptionalTypePrivatePaymentDetailsData.write(value.`payout`, buf) + FfiConverterOptionalSequenceString.write(value.`claims`, buf) + } +} + + + +data class SelectedPayinMethodData ( + var `kind`: kotlin.String, + var `paymentDetailsHash`: kotlin.String?, + var `amount`: kotlin.String +) { + + companion object +} + +public object FfiConverterTypeSelectedPayinMethodData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SelectedPayinMethodData { + return SelectedPayinMethodData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: SelectedPayinMethodData) = ( + FfiConverterString.allocationSize(value.`kind`) + + FfiConverterOptionalString.allocationSize(value.`paymentDetailsHash`) + + FfiConverterString.allocationSize(value.`amount`) + ) + + override fun write(value: SelectedPayinMethodData, buf: ByteBuffer) { + FfiConverterString.write(value.`kind`, buf) + FfiConverterOptionalString.write(value.`paymentDetailsHash`, buf) + FfiConverterString.write(value.`amount`, buf) + } +} + + + +data class SelectedPayoutMethodData ( + var `kind`: kotlin.String, + var `paymentDetailsHash`: kotlin.String? +) { + + companion object +} + +public object FfiConverterTypeSelectedPayoutMethodData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SelectedPayoutMethodData { + return SelectedPayoutMethodData( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: SelectedPayoutMethodData) = ( + FfiConverterString.allocationSize(value.`kind`) + + FfiConverterOptionalString.allocationSize(value.`paymentDetailsHash`) + ) + + override fun write(value: SelectedPayoutMethodData, buf: ByteBuffer) { + FfiConverterString.write(value.`kind`, buf) + FfiConverterOptionalString.write(value.`paymentDetailsHash`, buf) + } +} + + + +data class ServiceData ( + var `id`: kotlin.String, + var `type`: kotlin.String, + var `serviceEndpoint`: List +) { + + companion object +} + +public object FfiConverterTypeServiceData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ServiceData { + return ServiceData( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterSequenceString.read(buf), + ) + } + + override fun allocationSize(value: ServiceData) = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterString.allocationSize(value.`type`) + + FfiConverterSequenceString.allocationSize(value.`serviceEndpoint`) + ) + + override fun write(value: ServiceData, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterString.write(value.`type`, buf) + FfiConverterSequenceString.write(value.`serviceEndpoint`, buf) + } +} + + + +data class VerificationMethodData ( + var `id`: kotlin.String, + var `type`: kotlin.String, + var `controller`: kotlin.String, + var `publicKeyJwk`: JwkData +) { + + companion object +} + +public object FfiConverterTypeVerificationMethodData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): VerificationMethodData { + return VerificationMethodData( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterTypeJwkData.read(buf), + ) + } + + override fun allocationSize(value: VerificationMethodData) = ( + FfiConverterString.allocationSize(value.`id`) + + FfiConverterString.allocationSize(value.`type`) + + FfiConverterString.allocationSize(value.`controller`) + + FfiConverterTypeJwkData.allocationSize(value.`publicKeyJwk`) + ) + + override fun write(value: VerificationMethodData, buf: ByteBuffer) { + FfiConverterString.write(value.`id`, buf) + FfiConverterString.write(value.`type`, buf) + FfiConverterString.write(value.`controller`, buf) + FfiConverterTypeJwkData.write(value.`publicKeyJwk`, buf) + } +} + + + + +enum class MessageKind { + + RFQ, + QUOTE, + ORDER, + ORDER_STATUS, + CLOSE; + companion object +} + + +public object FfiConverterTypeMessageKind: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = try { + MessageKind.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: MessageKind) = 4UL + + override fun write(value: MessageKind, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + + + +enum class Optionality { + + REQUIRED, + PREFERRED; + companion object +} + + +public object FfiConverterTypeOptionality: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = try { + Optionality.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: Optionality) = 4UL + + override fun write(value: Optionality, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + + + +enum class ResourceKind { + + OFFERING, + BALANCE; + companion object +} + + +public object FfiConverterTypeResourceKind: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = try { + ResourceKind.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: ResourceKind) = 4UL + + override fun write(value: ResourceKind, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + + + + + +public object FfiConverterOptionalBoolean: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.Boolean? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterBoolean.read(buf) + } + + override fun allocationSize(value: kotlin.Boolean?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterBoolean.allocationSize(value) + } + } + + override fun write(value: kotlin.Boolean?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterBoolean.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalString: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.String? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterString.read(buf) + } + + override fun allocationSize(value: kotlin.String?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterString.allocationSize(value) + } + } + + override fun write(value: kotlin.String?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterString.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypeClose: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Close? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeClose.read(buf) + } + + override fun allocationSize(value: Close?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeClose.allocationSize(value) + } + } + + override fun write(value: Close?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeClose.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypeOrder: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Order? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeOrder.read(buf) + } + + override fun allocationSize(value: Order?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeOrder.allocationSize(value) + } + } + + override fun write(value: Order?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeOrder.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypeQuote: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Quote? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeQuote.read(buf) + } + + override fun allocationSize(value: Quote?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeQuote.allocationSize(value) + } + } + + override fun write(value: Quote?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeQuote.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypeFilterData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FilterData? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeFilterData.read(buf) + } + + override fun allocationSize(value: FilterData?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeFilterData.allocationSize(value) + } + } + + override fun write(value: FilterData?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeFilterData.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypePaymentInstructionsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PaymentInstructionsData? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypePaymentInstructionsData.read(buf) + } + + override fun allocationSize(value: PaymentInstructionsData?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypePaymentInstructionsData.allocationSize(value) + } + } + + override fun write(value: PaymentInstructionsData?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypePaymentInstructionsData.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypePresentationDefinitionData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PresentationDefinitionData? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypePresentationDefinitionData.read(buf) + } + + override fun allocationSize(value: PresentationDefinitionData?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypePresentationDefinitionData.allocationSize(value) + } + } + + override fun write(value: PresentationDefinitionData?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypePresentationDefinitionData.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypePrivatePaymentDetailsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PrivatePaymentDetailsData? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypePrivatePaymentDetailsData.read(buf) + } + + override fun allocationSize(value: PrivatePaymentDetailsData?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypePrivatePaymentDetailsData.allocationSize(value) + } + } + + override fun write(value: PrivatePaymentDetailsData?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypePrivatePaymentDetailsData.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypeOptionality: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Optionality? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeOptionality.read(buf) + } + + override fun allocationSize(value: Optionality?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeOptionality.allocationSize(value) + } + } + + override fun write(value: Optionality?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeOptionality.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalSequenceString: FfiConverterRustBuffer?> { + override fun read(buf: ByteBuffer): List? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterSequenceString.read(buf) + } + + override fun allocationSize(value: List?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterSequenceString.allocationSize(value) + } + } + + override fun write(value: List?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterSequenceString.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalSequenceTypeOrderStatus: FfiConverterRustBuffer?> { + override fun read(buf: ByteBuffer): List? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterSequenceTypeOrderStatus.read(buf) + } + + override fun allocationSize(value: List?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterSequenceTypeOrderStatus.allocationSize(value) + } + } + + override fun write(value: List?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterSequenceTypeOrderStatus.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalSequenceTypeServiceData: FfiConverterRustBuffer?> { + override fun read(buf: ByteBuffer): List? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterSequenceTypeServiceData.read(buf) + } + + override fun allocationSize(value: List?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterSequenceTypeServiceData.allocationSize(value) + } + } + + override fun write(value: List?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterSequenceTypeServiceData.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalMapStringString: FfiConverterRustBuffer?> { + override fun read(buf: ByteBuffer): Map? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterMapStringString.read(buf) + } + + override fun allocationSize(value: Map?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterMapStringString.allocationSize(value) + } + } + + override fun write(value: Map?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterMapStringString.write(value, buf) + } + } +} + + + + +public object FfiConverterSequenceUByte: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterUByte.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterUByte.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterUByte.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceString: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterString.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterString.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterString.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeBalance: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeBalance.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeBalance.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeBalance.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeOffering: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeOffering.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeOffering.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeOffering.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeOrderStatus: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeOrderStatus.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeOrderStatus.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeOrderStatus.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeFieldData: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeFieldData.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeFieldData.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeFieldData.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeInputDescriptorData: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeInputDescriptorData.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeInputDescriptorData.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeInputDescriptorData.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypePayinMethodData: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypePayinMethodData.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypePayinMethodData.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypePayinMethodData.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypePayoutMethodData: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypePayoutMethodData.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypePayoutMethodData.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypePayoutMethodData.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeServiceData: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeServiceData.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeServiceData.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeServiceData.write(it, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeVerificationMethodData: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeVerificationMethodData.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeVerificationMethodData.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeVerificationMethodData.write(it, buf) + } + } +} + + + +public object FfiConverterMapStringString: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): Map { + val len = buf.getInt() + return buildMap(len) { + repeat(len) { + val k = FfiConverterString.read(buf) + val v = FfiConverterString.read(buf) + this[k] = v + } + } + } + + override fun allocationSize(value: Map): ULong { + val spaceForMapSize = 4UL + val spaceForChildren = value.map { (k, v) -> + FfiConverterString.allocationSize(k) + + FfiConverterString.allocationSize(v) + }.sum() + return spaceForMapSize + spaceForChildren + } + + override fun write(value: Map, buf: ByteBuffer) { + buf.putInt(value.size) + // The parens on `(k, v)` here ensure we're calling the right method, + // which is important for compatibility with older android devices. + // Ref https://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/ + value.forEach { (k, v) -> + FfiConverterString.write(k, buf) + FfiConverterString.write(v, buf) + } + } +} + @Throws(RustCoreException::class) fun `createExchange`(`rfq`: Rfq, `replyTo`: kotlin.String?) + = + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_create_exchange( + FfiConverterTypeRfq.lower(`rfq`),FfiConverterOptionalString.lower(`replyTo`),_status) +} + + + + @Throws(RustCoreException::class) fun `getBalances`(`pfiDidUri`: kotlin.String, `bearerDid`: BearerDid): List { + return FfiConverterSequenceTypeBalance.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_get_balances( + FfiConverterString.lower(`pfiDidUri`),FfiConverterTypeBearerDid.lower(`bearerDid`),_status) +} + ) + } + + + @Throws(RustCoreException::class) fun `getExchange`(`pfiDidUri`: kotlin.String, `bearerDid`: BearerDid, `exchangeId`: kotlin.String): ExchangeData { + return FfiConverterTypeExchangeData.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_get_exchange( + FfiConverterString.lower(`pfiDidUri`),FfiConverterTypeBearerDid.lower(`bearerDid`),FfiConverterString.lower(`exchangeId`),_status) +} + ) + } + + + @Throws(RustCoreException::class) fun `getExchanges`(`pfiDidUri`: kotlin.String, `bearerDid`: BearerDid): List { + return FfiConverterSequenceString.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_get_exchanges( + FfiConverterString.lower(`pfiDidUri`),FfiConverterTypeBearerDid.lower(`bearerDid`),_status) +} + ) + } + + + @Throws(RustCoreException::class) fun `getOfferings`(`pfiDidUri`: kotlin.String): List { + return FfiConverterSequenceTypeOffering.lift( + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_get_offerings( + FfiConverterString.lower(`pfiDidUri`),_status) +} + ) + } + + + @Throws(RustCoreException::class) fun `submitClose`(`close`: Close) + = + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_submit_close( + FfiConverterTypeClose.lower(`close`),_status) +} + + + + @Throws(RustCoreException::class) fun `submitOrder`(`order`: Order) + = + uniffiRustCallWithError(RustCoreException) { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_submit_order( + FfiConverterTypeOrder.lower(`order`),_status) +} + + + fun `tmpHackBearerDid`(`did`: DidData, `document`: DocumentData, `keyManager`: KeyManager): BearerDid { + return FfiConverterTypeBearerDid.lift( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_tbdex_uniffi_fn_func_tmp_hack_bearer_did( + FfiConverterTypeDidData.lower(`did`),FfiConverterTypeDocumentData.lower(`document`),FfiConverterTypeKeyManager.lower(`keyManager`),_status) +} + ) + } diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/web5/BearerDid.kt b/bound/kt/src/main/kotlin/tbdex/sdk/web5/BearerDid.kt new file mode 100644 index 00000000..0a84ea03 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/web5/BearerDid.kt @@ -0,0 +1,41 @@ +package tbdex.sdk.web5 + +import tbdex.sdk.rust.tmpHackBearerDid +import tbdex.sdk.rust.DidData as RustCoreDid +import tbdex.sdk.rust.DocumentData as RustCoreDocument +import tbdex.sdk.rust.BearerDid as RustCoreBearerDid + +typealias Did = RustCoreDid +typealias Document = RustCoreDocument + +class BearerDid { + val did: Did + val document: Document + val keyManager: KeyManager + + val rustCoreBearerDid: RustCoreBearerDid + + constructor(uri: String, keyManager: KeyManager) { + this.rustCoreBearerDid = RustCoreBearerDid(uri, keyManager.getRustCoreKeyManager()) + + this.did = this.rustCoreBearerDid.getData().did + this.document = this.rustCoreBearerDid.getData().document + this.keyManager = keyManager + } + + // TODO temporarily doing this because did:dht resolution incomplete + constructor(did: Did, document: Document, keyManager: KeyManager) { + this.did = did + this.document = document + this.keyManager = keyManager + + this.rustCoreBearerDid = tmpHackBearerDid(did, document, keyManager.getRustCoreKeyManager()) + } + + fun getSigner(): Signer { + // TODO currently hardcoding to first VM + val keyId = this.document.verificationMethod.first().id + val innerSigner = this.rustCoreBearerDid.getSigner(keyId) + return OuterSigner(innerSigner) + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/web5/InMemoryKeyManager.kt b/bound/kt/src/main/kotlin/tbdex/sdk/web5/InMemoryKeyManager.kt new file mode 100644 index 00000000..cbb24530 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/web5/InMemoryKeyManager.kt @@ -0,0 +1,27 @@ +package tbdex.sdk.web5 + +import tbdex.sdk.rust.InMemoryKeyManager as RustCoreInMemoryKeyManager +import tbdex.sdk.rust.KeyManager as RustCoreKeyManager + +class InMemoryKeyManager : KeyManager { + private val rustCoreInMemoryKeyManager = RustCoreInMemoryKeyManager() + + constructor(privateJwks: List) { + privateJwks.forEach { + this.rustCoreInMemoryKeyManager.importPrivateJwk(it) + } + } + + override fun getSigner(publicJwk: Jwk): Signer { + val innerSigner = this.rustCoreInMemoryKeyManager.getSigner(publicJwk) + return OuterSigner(innerSigner) + } + + override fun getRustCoreKeyManager(): RustCoreKeyManager { + return this.rustCoreInMemoryKeyManager.getAsKeyManager() + } + + fun importPrivateJwk(privateJwk: Jwk): Jwk { + return this.rustCoreInMemoryKeyManager.importPrivateJwk(privateJwk) + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/web5/KeyManager.kt b/bound/kt/src/main/kotlin/tbdex/sdk/web5/KeyManager.kt new file mode 100644 index 00000000..768179b0 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/web5/KeyManager.kt @@ -0,0 +1,11 @@ +package tbdex.sdk.web5 + +import tbdex.sdk.rust.JwkData as RustCoreJwk +import tbdex.sdk.rust.KeyManager as RustCoreKeyManager + +typealias Jwk = RustCoreJwk + +interface KeyManager { + fun getSigner(publicJwk: Jwk): Signer + fun getRustCoreKeyManager(): RustCoreKeyManager +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/web5/PresentationDefinition.kt b/bound/kt/src/main/kotlin/tbdex/sdk/web5/PresentationDefinition.kt new file mode 100644 index 00000000..cc98f738 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/web5/PresentationDefinition.kt @@ -0,0 +1,40 @@ +package tbdex.sdk.web5 + +import tbdex.sdk.rust.InputDescriptorData as RustCoreInputDescriptor +import tbdex.sdk.rust.PresentationDefinition as RustCorePresentationDefinition +import tbdex.sdk.rust.PresentationDefinitionData as RustCorePresentationDefinitionData + +typealias InputDescriptor = RustCoreInputDescriptor + +class PresentationDefinition { + val id: String + val name: String? + val purpose: String? + val inputDescriptors: List + + val rustCorePresentationDefinition: RustCorePresentationDefinition + + constructor(id: String, name: String? = null, purpose: String? = null, inputDescriptors: List) { + this.id = id + this.name = name + this.purpose = purpose + this.inputDescriptors = inputDescriptors + + this.rustCorePresentationDefinition = RustCorePresentationDefinition( + RustCorePresentationDefinitionData(id, name, purpose, inputDescriptors) + ) + } + + constructor(rustCorePresentationDefinitionData: RustCorePresentationDefinitionData) { + this.id = rustCorePresentationDefinitionData.id + this.name = rustCorePresentationDefinitionData.name + this.purpose = rustCorePresentationDefinitionData.purpose + this.inputDescriptors = rustCorePresentationDefinitionData.inputDescriptors + + this.rustCorePresentationDefinition = RustCorePresentationDefinition(rustCorePresentationDefinitionData) + } + + fun selectCredentials(vcJwts: List): List { + return this.rustCorePresentationDefinition.selectCredentials(vcJwts) + } +} \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/tbdex/sdk/web5/Signer.kt b/bound/kt/src/main/kotlin/tbdex/sdk/web5/Signer.kt new file mode 100644 index 00000000..cb78e379 --- /dev/null +++ b/bound/kt/src/main/kotlin/tbdex/sdk/web5/Signer.kt @@ -0,0 +1,20 @@ +package tbdex.sdk.web5 + +import tbdex.sdk.rust.Signer as RustCoreSigner + +interface Signer { + fun sign(payload: ByteArray): ByteArray +} + +class OuterSigner : Signer { + private val rustCoreSigner: RustCoreSigner + + constructor(rustCoreSigner: RustCoreSigner) { + this.rustCoreSigner = rustCoreSigner + } + + @OptIn(ExperimentalUnsignedTypes::class) + override fun sign(payload: ByteArray): ByteArray { + return this.rustCoreSigner.sign(payload.toUByteArray().toList()) + } +} \ No newline at end of file diff --git a/bound/kt/src/main/resources/natives/libtbdex_uniffi.dylib b/bound/kt/src/main/resources/natives/libtbdex_uniffi.dylib index 3a3f4661..04f75482 100755 Binary files a/bound/kt/src/main/resources/natives/libtbdex_uniffi.dylib and b/bound/kt/src/main/resources/natives/libtbdex_uniffi.dylib differ diff --git a/bound/kt/src/test/kotlin/tbdex/sdk/HelloWorldTest.kt b/bound/kt/src/test/kotlin/tbdex/sdk/HelloWorldTest.kt deleted file mode 100644 index 9e9165d1..00000000 --- a/bound/kt/src/test/kotlin/tbdex/sdk/HelloWorldTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package tbdex.sdk - -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import tbdex.sdk.rust.helloWorld - -class HelloWorldTests { - @Test - fun `can helloWorld`() { - helloWorld() - } -} \ No newline at end of file diff --git a/bound/kt/src/test/kotlin/tbdex/sdk/httpclient/E2ePfiExemplarIntegrationTest.kt b/bound/kt/src/test/kotlin/tbdex/sdk/httpclient/E2ePfiExemplarIntegrationTest.kt new file mode 100644 index 00000000..b4725ee1 --- /dev/null +++ b/bound/kt/src/test/kotlin/tbdex/sdk/httpclient/E2ePfiExemplarIntegrationTest.kt @@ -0,0 +1,112 @@ +package tbdex.sdk.httpclient + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.fail +import org.junit.jupiter.api.Test +import tbdex.sdk.messages.* +import tbdex.sdk.rust.RustCoreException +import tbdex.sdk.rust.VerificationMethodData +import tbdex.sdk.rust.Web5RustCoreException +import tbdex.sdk.web5.* + +class E2ePfiExemplarIntegrationTest { + @Test + fun `can make the happy path`() { + try { + val pfiDidUri = "did:dht:swit41ctrddy1s38c5j46yfgbxmwo1emau71zo5hn1tws1g63hiy" + + val didUri = "did:dht:1fs5hnxsgtxgdr4wzqi38cnj46b1whhn94ojwo66g8hsc5bt3fgy" + val keyManager = InMemoryKeyManager(listOf()) + val publicJwk = keyManager.importPrivateJwk(Jwk( + "EdDSA", + "OKP", + "Ed25519", + "jVOdpSIN-DhddW_XVnDipukuzu6-8zieXQtkECZYJ04", + "kW2-CfY0XmGTVLurk7BJ14Mqc4L-oJpD3jH5ZmwxyUw", + null + )) + val did = Did(didUri, "", "", "", null, null, null, null) + val bearerDid = BearerDid(did, Document( + didUri, null, null, null, + listOf(VerificationMethodData( + "$didUri#0", + "JsonWebKey", + didUri, + publicJwk + )), + null, null, null, null, null, null + ), keyManager) + + // get offerings + val offerings = getOfferings(pfiDidUri) + println("Successfully retrieved offerings") + assertNotEquals(0, offerings.size) + + // get balance + // TODO pfi-exemplar currently returning invalid balances (missing signature) +// val balances = getBalances(pfiDidUri, bearerDid) +// println("Successfully retrieved balances") +// assertNotEquals(0, balances.size) + + // create exchange + val rfq = Rfq( + bearerDid, + pfiDidUri, + bearerDid.did.uri, + CreateRfqData( + offeringId = offerings[0].metadata.id, + payin = CreateSelectedPayinMethod( + "USD_LEDGER", + null, + "101" + ), + payout = CreateSelectedPayoutMethod( + "MOMO_MPESA", + ObjectMapper().readTree("""{ + "phoneNumber": "867-5309", + "reason": "cause" + }""") + ), + claims = listOf("eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6YzhkOWh1azduaG9tNG43emdybWE2cGp5Y3k2NzR1cmFhNHBvcDl1dXQ0MWdiOXd5OHNueSMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiU2FuY3Rpb25DcmVkZW50aWFsIl0sImlkIjoidXJuOnV1aWQ6ZjBkYWNlZmItNDVlNy00YWEyLTkxNDctMTZmYTBiYzc3ZTVjIiwiaXNzdWVyIjoiZGlkOmRodDpjOGQ5aHVrN25ob200bjd6Z3JtYTZwanljeTY3NHVyYWE0cG9wOXV1dDQxZ2I5d3k4c255IiwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNi0yNFQxNDoxNTozNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDpkaHQ6MWZzNWhueHNndHhnZHI0d3pxaTM4Y25qNDZiMXdoaG45NG9qd282Nmc4aHNjNWJ0M2ZneSIsImJlZXAiOiJib29wIn19LCJuYmYiOjE3MTkyMzg1MzUsImp0aSI6InVybjp1dWlkOmYwZGFjZWZiLTQ1ZTctNGFhMi05MTQ3LTE2ZmEwYmM3N2U1YyIsImlzcyI6ImRpZDpkaHQ6YzhkOWh1azduaG9tNG43emdybWE2cGp5Y3k2NzR1cmFhNHBvcDl1dXQ0MWdiOXd5OHNueSIsInN1YiI6ImRpZDpkaHQ6MWZzNWhueHNndHhnZHI0d3pxaTM4Y25qNDZiMXdoaG45NG9qd282Nmc4aHNjNWJ0M2ZneSIsImlhdCI6MTcxOTIzODUzNX0.DvDFIl8BTuHRk7VkB82OhYpX0WzBb3BucvAqfXiS92QCiRokXCgQAsOwbbSODoDaFWbHG0BJmWM-eDPcCoucCw") + ), + "1.0", null + ) + createExchange(rfq, null) + println("Successfully created exchange") + + // get quote + var exchange = getExchange(pfiDidUri, bearerDid, rfq.metadata.exchangeId) + val quote = exchange.quote ?: throw Exception("Quote should not be null") + println("Successfully retrieved quote") + + // submit order + submitOrder(Order( + bearerDid, + pfiDidUri, + bearerDid.did.uri, + quote.metadata.exchangeId, + "1.0", null + )) + println("Successfully submitted order") + + // get order status and close + var count = 0 + while (exchange.close == null) { + Thread.sleep(5000) + exchange = getExchange(pfiDidUri, bearerDid, quote.metadata.exchangeId) + count += 1 + if (count >= 3) { + throw Exception("Tried 3 times to fetch order status and close and failed") + } + } + + println("Exchange completed successfully!") + } catch (ex: RustCoreException) { + fail("RustCoreException caught || ${ex.errorType()} || ${ex.variant()} || ${ex.message()}") + } catch (ex: Web5RustCoreException) { + fail("Web5RustCoreException caught || ${ex.errorType()} || ${ex.variant()} || ${ex.message()}") + } + } +} \ No newline at end of file diff --git a/bound/kt/src/test/kotlin/tbdex/sdk/resources/OfferingTests.kt b/bound/kt/src/test/kotlin/tbdex/sdk/resources/OfferingTests.kt new file mode 100644 index 00000000..6199b424 --- /dev/null +++ b/bound/kt/src/test/kotlin/tbdex/sdk/resources/OfferingTests.kt @@ -0,0 +1,10 @@ +package tbdex.sdk.resources + +import org.junit.jupiter.api.Test + +class OfferingTests { + @Test + fun `can helloWorld`() { + println("Hello World") + } +} \ No newline at end of file diff --git a/crates/pfi_exemplar_integration_test/Cargo.toml b/crates/pfi_exemplar_integration_test/Cargo.toml new file mode 100644 index 00000000..55e48f9e --- /dev/null +++ b/crates/pfi_exemplar_integration_test/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pfi_exemplar_integration_test" +version = "0.1.0" +edition = "2021" +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +[dependencies] +serde_json = { workspace = true } +tbdex = { path = "../tbdex" } +web5 = { workspace = true } \ No newline at end of file diff --git a/crates/pfi_exemplar_integration_test/src/main.rs b/crates/pfi_exemplar_integration_test/src/main.rs new file mode 100644 index 00000000..5147d8c3 --- /dev/null +++ b/crates/pfi_exemplar_integration_test/src/main.rs @@ -0,0 +1,126 @@ +use std::{sync::Arc, time::Duration}; +use tbdex::{ + http_client::{ + exchanges::{create_exchange, get_exchange, submit_order, Exchange}, + offerings::get_offerings, + }, + messages::{ + order::Order, + rfq::{CreateRfqData, CreateSelectedPayinMethod, CreateSelectedPayoutMethod, Rfq}, + }, +}; +use web5::apid::{ + crypto::{jwk::Jwk, key_managers::in_memory_key_manager::InMemoryKeyManager}, + dids::{ + bearer_did::BearerDid, + data_model::{document::Document, verification_method::VerificationMethod}, + did::Did, + }, +}; + +fn main() { + let pfi_did_uri = "did:dht:swit41ctrddy1s38c5j46yfgbxmwo1emau71zo5hn1tws1g63hiy".to_string(); + + let did_uri = "did:dht:1fs5hnxsgtxgdr4wzqi38cnj46b1whhn94ojwo66g8hsc5bt3fgy".to_string(); + let key_manager = InMemoryKeyManager::new(); + key_manager + .import_private_jwk(Jwk { + crv: "Ed25519".to_string(), + alg: "EdDSA".to_string(), + kty: "OKP".to_string(), + x: "kW2-CfY0XmGTVLurk7BJ14Mqc4L-oJpD3jH5ZmwxyUw".to_string(), + y: None, + d: Some("jVOdpSIN-DhddW_XVnDipukuzu6-8zieXQtkECZYJ04".to_string()), + }) + .unwrap(); + let bearer_did = BearerDid { + did: Did::new(&did_uri).unwrap(), + document: Document { + id: did_uri.clone(), + verification_method: vec![VerificationMethod { + id: format!("{}#0", did_uri), + r#type: "JsonWebKey".to_string(), + controller: did_uri.clone(), + public_key_jwk: Jwk { + crv: "Ed25519".to_string(), + kty: "OKP".to_string(), + alg: "EdDSA".to_string(), + x: "kW2-CfY0XmGTVLurk7BJ14Mqc4L-oJpD3jH5ZmwxyUw".to_string(), + ..Default::default() + }, + }], + ..Default::default() + }, + key_manager: Arc::new(key_manager), + }; + + // request offerings + let offerings = get_offerings(&pfi_did_uri).unwrap(); + assert_ne!(0, offerings.len()); + + // TODO pfi exemplar balances are missing `signature` + // // request balance + // let balances = get_balances(&pfi_did_uri, &bearer_did).unwrap(); + // assert_ne!(0, balances.len()); + + // create exchange + let rfq = Rfq::new( + &bearer_did, + &pfi_did_uri, + &bearer_did.did.uri, + &CreateRfqData { + offering_id: offerings[0].metadata.id.clone(), + payin: CreateSelectedPayinMethod { + kind: "USD_LEDGER".to_string(), + payment_details: None, + amount: "101".to_string(), + }, + payout: CreateSelectedPayoutMethod { + kind: "MOMO_MPESA".to_string(), + payment_details: Some(serde_json::json!({ + "phoneNumber": "867-5309", + "reason": "cause" + })), + }, + claims: vec!["eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6YzhkOWh1azduaG9tNG43emdybWE2cGp5Y3k2NzR1cmFhNHBvcDl1dXQ0MWdiOXd5OHNueSMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiU2FuY3Rpb25DcmVkZW50aWFsIl0sImlkIjoidXJuOnV1aWQ6ZjBkYWNlZmItNDVlNy00YWEyLTkxNDctMTZmYTBiYzc3ZTVjIiwiaXNzdWVyIjoiZGlkOmRodDpjOGQ5aHVrN25ob200bjd6Z3JtYTZwanljeTY3NHVyYWE0cG9wOXV1dDQxZ2I5d3k4c255IiwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNi0yNFQxNDoxNTozNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDpkaHQ6MWZzNWhueHNndHhnZHI0d3pxaTM4Y25qNDZiMXdoaG45NG9qd282Nmc4aHNjNWJ0M2ZneSIsImJlZXAiOiJib29wIn19LCJuYmYiOjE3MTkyMzg1MzUsImp0aSI6InVybjp1dWlkOmYwZGFjZWZiLTQ1ZTctNGFhMi05MTQ3LTE2ZmEwYmM3N2U1YyIsImlzcyI6ImRpZDpkaHQ6YzhkOWh1azduaG9tNG43emdybWE2cGp5Y3k2NzR1cmFhNHBvcDl1dXQ0MWdiOXd5OHNueSIsInN1YiI6ImRpZDpkaHQ6MWZzNWhueHNndHhnZHI0d3pxaTM4Y25qNDZiMXdoaG45NG9qd282Nmc4aHNjNWJ0M2ZneSIsImlhdCI6MTcxOTIzODUzNX0.DvDFIl8BTuHRk7VkB82OhYpX0WzBb3BucvAqfXiS92QCiRokXCgQAsOwbbSODoDaFWbHG0BJmWM-eDPcCoucCw".to_string()], + }, + "1.0", + None, + ) + .unwrap(); + + create_exchange(&rfq, None).unwrap(); + + // get quote + let exchange = get_exchange(&pfi_did_uri, &bearer_did, &rfq.metadata.exchange_id).unwrap(); + let quote = exchange.quote.unwrap(); + + // submit order + submit_order( + &Order::new( + &bearer_did, + &pfi_did_uri, + &bearer_did.did.uri, + "e.metadata.exchange_id, + "1.0", + None, + ) + .unwrap(), + ) + .unwrap(); + + // get order status and close + let mut exchange = Exchange::default(); + let mut count = 0; + while exchange.close.is_none() { + std::thread::sleep(Duration::from_secs(5)); + exchange = get_exchange(&pfi_did_uri, &bearer_did, &rfq.metadata.exchange_id).unwrap(); + + count += 1; + if count >= 3 { + panic!("Failed to close exchange after 3 attempts"); + } + } + + println!("Exchange completed successfully!") +} diff --git a/crates/protocol/Cargo.toml b/crates/protocol/Cargo.toml deleted file mode 100644 index e3b394fa..00000000 --- a/crates/protocol/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "protocol" -version = "0.1.0" -edition = "2021" -homepage.workspace = true -repository.workspace = true -license-file.workspace = true - -[dependencies] -chrono = { version = "0.4.31", features = ["serde"] } -credentials = { git = "https://github.com/TBD54566975/web5-rs", rev = "694db64b72a92e17880c87d31a4bf11fe3bb8793"} -jsonschema = "0.17.1" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" -type-safe-id = { version = "0.2.1", features = ["serde"] } -thiserror = "1.0.50" -serde_with = "3.4.0" \ No newline at end of file diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs deleted file mode 100644 index 019d6536..00000000 --- a/crates/protocol/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod messages; -pub mod resources; - -#[cfg(test)] -pub mod test_data; diff --git a/crates/protocol/src/messages/close.rs b/crates/protocol/src/messages/close.rs deleted file mode 100644 index d1494e61..00000000 --- a/crates/protocol/src/messages/close.rs +++ /dev/null @@ -1,83 +0,0 @@ -use ::serde::{Deserialize, Serialize}; -use chrono::Utc; -use serde_with::skip_serializing_none; -use type_safe_id::{DynamicType, TypeSafeId}; - -use super::{Message, MessageError, MessageKind, MessageMetadata}; - -pub struct Close; - -impl Close { - pub fn create( - from: String, - to: String, - exchange_id: TypeSafeId, - reason: Option, - ) -> Result, MessageError> { - let metadata = MessageMetadata { - from, - to, - kind: MessageKind::Close, - id: MessageKind::Close.typesafe_id()?, - exchange_id, - created_at: Utc::now(), - }; - - let data = CloseData { reason }; - - Ok(Message { - metadata, - data, - signature: None, - }) - } -} - -/// A struct representing the data contained within the [`Message`] for a [`Close`]. -/// -/// See [Quote](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#close) for more -/// information. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[skip_serializing_none] -#[serde(rename_all = "camelCase")] -pub struct CloseData { - /// an explanation of why the exchange is being closed/completed - reason: Option, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_data::TestData; - - #[test] - fn can_create() { - let close = Close::create( - "did:example:from_1234".to_string(), - "did:example:to_1234".to_string(), - MessageKind::Rfq.typesafe_id().unwrap(), - Some("I don't want to do business with you any more".to_string()), - ) - .expect("failed to create Close"); - - assert_eq!( - close.data.reason, - Some("I don't want to do business with you any more".to_string()) - ); - assert_eq!(close.metadata.id.type_prefix(), "close"); - } - - #[test] - fn can_parse_close_from_json() { - let close = TestData::get_close( - "did:example:from_1234".to_string(), - MessageKind::Rfq - .typesafe_id() - .expect("failed to generate exchange_id"), - ); - let json = serde_json::to_string(&close).expect("failed to serialize Close"); - let parsed_close: Message = - serde_json::from_str(&json).expect("failed to deserialize Close"); - assert_eq!(close, parsed_close); - } -} diff --git a/crates/protocol/src/messages/mod.rs b/crates/protocol/src/messages/mod.rs deleted file mode 100644 index 65d125d5..00000000 --- a/crates/protocol/src/messages/mod.rs +++ /dev/null @@ -1,89 +0,0 @@ -pub mod close; -pub mod order; -pub mod order_status; -pub mod quote; - -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use serde_json::to_string; -use type_safe_id::{DynamicType, TypeSafeId}; - -/// An enum representing all possible [`Message`] kinds. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum MessageKind { - Close, - Order, - OrderStatus, - Quote, - Rfq, -} - -/// A struct representing the metadata present on every [`Message`]. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MessageMetadata { - /// The message's ID - pub id: TypeSafeId, - /// This defines the data property's type (e.g. rfq, quote etc.) - pub kind: MessageKind, - /// ID for a "exchange" of messages between Alice <-> PFI. - /// Set by the first message in an exchange. - pub exchange_id: TypeSafeId, - /// The sender's DID - pub from: String, - /// The recipient's DID - pub to: String, - /// ISO 8601 - pub created_at: DateTime, -} - -/// A struct representing the structure and common functionality available to all Messages. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Message { - /// An object containing fields about the message - pub metadata: MessageMetadata, - /// The actual message content - pub data: T, - /// The signature that verifies the authenticity and integrity of the message - pub signature: Option, -} - -/// Errors that can occur when working with [`Message`]s. -#[derive(thiserror::Error, Debug)] -pub enum MessageError { - #[error(transparent)] - SerdeJsonError(#[from] serde_json::Error), - #[error(transparent)] - TypeSafeIdError(#[from] type_safe_id::Error), -} - -impl MessageKind { - /// Returns the [`TypeSafeId`] of the [`MessageKind`]. - pub fn typesafe_id(&self) -> Result, MessageError> { - let serialized_kind = to_string(&self)?; - let dynamic_type = DynamicType::new(serialized_kind.trim_matches('"'))?; - Ok(TypeSafeId::new_with_type(dynamic_type)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn message_kind_typesafe_id() { - let close_id = MessageKind::Close.typesafe_id().unwrap(); - let order_id = MessageKind::Order.typesafe_id().unwrap(); - let order_status_id = MessageKind::OrderStatus.typesafe_id().unwrap(); - let quote_id = MessageKind::Quote.typesafe_id().unwrap(); - let rfq_id = MessageKind::Rfq.typesafe_id().unwrap(); - - assert!(close_id.to_string().starts_with("close_")); - assert!(order_id.to_string().starts_with("order_")); - assert!(order_status_id.to_string().starts_with("orderstatus_")); - assert!(quote_id.to_string().starts_with("quote_")); - assert!(rfq_id.to_string().starts_with("rfq_")); - } -} diff --git a/crates/protocol/src/messages/order.rs b/crates/protocol/src/messages/order.rs deleted file mode 100644 index 40c59c02..00000000 --- a/crates/protocol/src/messages/order.rs +++ /dev/null @@ -1,72 +0,0 @@ -use ::serde::{Deserialize, Serialize}; -use chrono::Utc; -use type_safe_id::{DynamicType, TypeSafeId}; - -use super::{Message, MessageError, MessageKind, MessageMetadata}; - -pub struct Order; - -impl Order { - pub fn create( - from: String, - to: String, - exchange_id: TypeSafeId, - ) -> Result, MessageError> { - let metadata = MessageMetadata { - from, - to, - kind: MessageKind::Order, - id: MessageKind::Order.typesafe_id()?, - exchange_id, - created_at: Utc::now(), - }; - - let data = OrderData; - - Ok(Message { - metadata, - data, - signature: None, - }) - } -} - -/// A struct representing the data contained within the [`Message`] for an [`Order`]. -/// Currently, [`Order`] contains no data fields. -/// -/// See [Order](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#order) for more -/// information. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OrderData; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_create() { - let order = Order::create( - "did:example:from_1234".to_string(), - "did:example:to_1234".to_string(), - MessageKind::Rfq.typesafe_id().unwrap(), - ) - .expect("failed to create Order"); - - assert_eq!(order.metadata.id.type_prefix(), "order"); - } - - #[test] - fn can_parse_order_from_json() { - let order = Order::create( - "did:example:from_1234".to_string(), - "did:example:to_1234".to_string(), - MessageKind::Rfq.typesafe_id().unwrap(), - ) - .expect("Could not create Order"); - let json: String = serde_json::to_string(&order).expect("failed to serialize Order"); - let parsed_order: Message = - serde_json::from_str(&json).expect("failed to deserialize Order"); - assert_eq!(order, parsed_order); - } -} diff --git a/crates/protocol/src/messages/order_status.rs b/crates/protocol/src/messages/order_status.rs deleted file mode 100644 index ac49888b..00000000 --- a/crates/protocol/src/messages/order_status.rs +++ /dev/null @@ -1,79 +0,0 @@ -use ::serde::{Deserialize, Serialize}; -use chrono::Utc; -use type_safe_id::{DynamicType, TypeSafeId}; - -use super::{Message, MessageError, MessageKind, MessageMetadata}; - -pub struct OrderStatus; - -impl OrderStatus { - pub fn create( - from: String, - to: String, - exchange_id: TypeSafeId, - order_status: String, - ) -> Result, MessageError> { - let metadata = MessageMetadata { - from, - to, - kind: MessageKind::OrderStatus, - id: MessageKind::OrderStatus.typesafe_id()?, - exchange_id, - created_at: Utc::now(), - }; - - let data = OrderStatusData { order_status }; - - Ok(Message { - metadata, - data, - signature: None, - }) - } -} - -/// A struct representing the data contained within the [`Message`] for an [`OrderStatus`]. -/// Currently, [`OrderStatus`] contains no data fields. -/// -/// See [OrderStatus](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#orderstatus) for more -/// information. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OrderStatusData { - /// Current status of Order that's being executed - order_status: String, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_create() { - let order_status: Message = OrderStatus::create( - "did:example:from_1234".to_string(), - "did:example:to_1234".to_string(), - MessageKind::Rfq.typesafe_id().unwrap(), - "COMPLETED".to_string(), - ) - .expect("failed to create OrderStatus"); - - assert_eq!(order_status.metadata.id.type_prefix(), "orderstatus"); - } - - #[test] - fn can_parse_order_status_from_json() { - let order_status = OrderStatus::create( - "did:example:from_1234".to_string(), - "did:example:to_1234".to_string(), - MessageKind::Rfq.typesafe_id().unwrap(), - "COMPLETED".to_string(), - ) - .expect("Could not create OrderStatus"); - let json: String = - serde_json::to_string(&order_status).expect("failed to serialize OrderStatus"); - let parsed_order_status: Message = - serde_json::from_str(&json).expect("failed to deserialize OrderStatus"); - assert_eq!(order_status, parsed_order_status); - } -} diff --git a/crates/protocol/src/messages/quote.rs b/crates/protocol/src/messages/quote.rs deleted file mode 100644 index 6cbaa00c..00000000 --- a/crates/protocol/src/messages/quote.rs +++ /dev/null @@ -1,139 +0,0 @@ -use ::serde::{Deserialize, Serialize}; -use chrono::{DateTime, Utc}; -use serde_with::skip_serializing_none; -use type_safe_id::{DynamicType, TypeSafeId}; - -use super::{Message, MessageError, MessageKind, MessageMetadata}; - -pub struct Quote; - -impl Quote { - pub fn create( - from: String, - to: String, - exchange_id: TypeSafeId, - data: QuoteData, - ) -> Result, MessageError> { - let metadata = MessageMetadata { - from, - to, - kind: MessageKind::Quote, - id: MessageKind::Quote.typesafe_id()?, - exchange_id, - created_at: Utc::now(), - }; - - Ok(Message { - metadata, - data, - signature: None, - }) - } -} - -/// A struct representing the data contained within the [`Message`] for a [`Quote`]. -/// -/// See [Quote](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#quote) for more -/// information. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[skip_serializing_none] -#[serde(rename_all = "camelCase")] -pub struct QuoteData { - /// When this quote expires. Expressed as ISO8601 - pub expires_at: DateTime, - /// the amount of payin currency that the PFI will receive - pub payin: QuoteDetails, - /// the amount of payout currency that Alice will receive - pub payout: QuoteDetails, - /// Object that describes how to pay the PFI, and how to get paid by the PFI (e.g. BTC address, payment link) - pub payment_instructions: Option, -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[skip_serializing_none] -#[serde(rename_all = "camelCase")] -pub struct QuoteDetails { - /// ISO 3166 currency code string - pub currency_code: String, - /// The amount of currency expressed in the smallest respective unit - pub amount_subunits: String, - /// The amount paid in fees expressed in the smallest respectice unit - pub fee_subunits: Option, -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[skip_serializing_none] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstructions { - /// Link or Instruction describing how to pay the PFI. - pub payin: Option, - /// Link or Instruction describing how to get paid by the PFI - pub payout: Option, -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[skip_serializing_none] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstruction { - /// Link or Instruction describing how to pay the PFI. - pub link: Option, - /// Instruction on how Alice can pay PFI, or how Alice can be paid by the PFI - pub instruction: Option, -} - -#[cfg(test)] -mod tests { - use crate::test_data::TestData; - - use super::*; - - #[test] - fn can_create() { - let quote = Quote::create( - "did:example:from_1234".to_string(), - "did:example:to_1234".to_string(), - MessageKind::Rfq.typesafe_id().unwrap(), - QuoteData { - expires_at: Utc::now(), - payin: QuoteDetails { - currency_code: "USD".to_string(), - amount_subunits: "100".to_string(), - fee_subunits: Some("10".to_string()), - }, - payout: QuoteDetails { - currency_code: "BTC".to_string(), - amount_subunits: "2500".to_string(), - fee_subunits: None, - }, - payment_instructions: Some(PaymentInstructions { - payin: Some(PaymentInstruction { - link: Some("example.com/payin".to_string()), - instruction: Some("Hand me the cash".to_string()), - }), - payout: Some(PaymentInstruction { - link: None, - instruction: Some("BOLT 12".to_string()), - }), - }), - }, - ) - .expect("failed to create Quote"); - - assert_eq!(quote.metadata.id.type_prefix(), "quote"); - } - - #[test] - fn can_parse_quote_from_json() { - let quote = TestData::get_quote( - "did:example:from_1234".to_string(), - "did:example:to_1234".to_string(), - MessageKind::Rfq - .typesafe_id() - .expect("failed to generate exchange_id"), - ); - let json = serde_json::to_string("e).expect("failed to serialize Quote"); - let parsed_quote: Message = - serde_json::from_str(&json).expect("failed to deserialize Quote"); - assert_eq!(quote, parsed_quote); - } -} diff --git a/crates/protocol/src/resources/mod.rs b/crates/protocol/src/resources/mod.rs deleted file mode 100644 index 158bb676..00000000 --- a/crates/protocol/src/resources/mod.rs +++ /dev/null @@ -1,73 +0,0 @@ -pub mod offering; - -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use serde_json::to_string; -use type_safe_id::{DynamicType, TypeSafeId}; - -/// An enum representing all possible [`Resource`] kinds. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum ResourceKind { - Offering, -} - -/// A struct representing the metadata present on every [`Resource`]. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ResourceMetadata { - /// The resource's ID - pub id: TypeSafeId, - /// This defines the data property's type (e.g. offering) - pub kind: ResourceKind, - /// The author's DID - pub from: String, - /// ISO 8601 timestamp - pub created_at: DateTime, - /// ISO 8601 timestamp - pub updated_at: Option>, - /// Version of the protocol in use (x.x format). The protocol version must remain consistent across messages in a given exchange. Messages sharing the same exchangeId MUST also have the same protocol version. - pub protocol: String, -} - -/// A struct representing the structure and common functionality available to all Resources. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Resource { - /// An object containing fields about the Resource - pub metadata: ResourceMetadata, - /// The actual Resource content - pub data: T, - /// The signature that verifies the authenticity and integrity of the Resource - pub signature: String, -} - -/// Errors that can occur when working with [`Resource`]s. -#[derive(thiserror::Error, Debug)] -pub enum ResourceError { - #[error(transparent)] - SerdeJsonError(#[from] serde_json::Error), - #[error(transparent)] - TypeSafeIdError(#[from] type_safe_id::Error), -} - -impl ResourceKind { - /// Returns the [`TypeSafeId`] of the [`ResourceKind`]. - pub fn typesafe_id(&self) -> Result, ResourceError> { - let serialized_kind = to_string(&self)?; - let dynamic_type = DynamicType::new(serialized_kind.trim_matches('"'))?; - Ok(TypeSafeId::new_with_type(dynamic_type)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn resource_kind_typesafe_id() { - let offering_id = ResourceKind::Offering.typesafe_id().unwrap(); - - assert!(offering_id.to_string().starts_with("offering_")); - } -} diff --git a/crates/protocol/src/resources/offering.rs b/crates/protocol/src/resources/offering.rs deleted file mode 100644 index 9f24e961..00000000 --- a/crates/protocol/src/resources/offering.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::resources::{Resource, ResourceError, ResourceKind, ResourceMetadata}; -use chrono::Utc; -use credentials::pex::v2::PresentationDefinition; -use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; - -/// Struct that interacts with an [`Offering`] [`Resource`] -pub struct Offering; - -/// Struct for passing parameters to [`Offering::create`] -#[derive(Debug, Default)] -pub struct CreateOptions { - pub from: String, - pub protocol: Option, - pub data: OfferingData, -} - -impl Offering { - pub fn create(options: CreateOptions) -> Result, ResourceError> { - let metadata = ResourceMetadata { - id: ResourceKind::Offering.typesafe_id()?, - kind: ResourceKind::Offering, - from: options.from, - created_at: Utc::now(), - updated_at: Some(Utc::now()), - protocol: match options.protocol { - Some(p) => p, - None => "1.0".to_string(), - }, - }; - - // todo implement signing https://github.com/TBD54566975/tbdex-rs/issues/27 - let signature = "todo a valid signature".to_string(); - - Ok(Resource { - metadata, - data: options.data, - signature, - }) - } -} - -/// Struct the data contained within the [`Resource`] for an [`Offering`]. -/// -/// See [Offering](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#offering) for more -/// information. -#[derive(Debug, Deserialize, PartialEq, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct OfferingData { - /// Brief description of what is being offered. - pub description: String, - /// Number of payout units Alice would get for 1 payin unit - pub payout_units_per_payin_unit: String, - /// Details and options associated to the payin currency - pub payin: PayinDetails, - /// Details and options associated to the payout currency - pub payout: PayoutDetails, - /// Claim(s) required when submitting an RFQ for this offering. - pub required_claims: PresentationDefinition, -} - -/// Struct for [Offering's PayinDetails](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#payindetails) -#[derive(Debug, Deserialize, PartialEq, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct PayinDetails { - /// ISO 4217 currency code string - pub currency_code: String, - /// Minimum amount of currency that the offer is valid for - pub min: Option, - /// Maximum amount of currency that the offer is valid for - pub max: Option, - /// A list of payment methods to select from - pub methods: Vec, -} - -/// Struct for [Offering's PayinMethod](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#payinmethod) -#[derive(Debug, Deserialize, PartialEq, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct PayinMethod { - /// Unique string identifying a single kind of payment method i.e. (i.e. DEBIT_CARD, BITCOIN_ADDRESS, SQUARE_PAY) - pub kind: String, - /// Payment Method name. Expected to be rendered on screen. - pub name: Option, - /// Blurb containing helpful information about the payment method. Expected to be rendered on screen. e.g. "segwit addresses only" - pub description: Option, - /// The category for which the given method belongs to - pub group: Option, - /// A JSON Schema containing the fields that need to be collected in the RFQ's selected payment methods in order to use this payment method. - pub required_payment_details: Option, - /// Fee charged to use this payment method. absence of this field implies that there is no additional fee associated to the respective payment method - pub fee: Option, - /// Minimum amount required to use this payment method. - pub min: Option, - /// Maximum amount allowed when using this payment method. - pub max: Option, -} - -/// Struct for [Offering's PayoutDetails](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#payoutdetails) -#[derive(Debug, Deserialize, PartialEq, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct PayoutDetails { - /// ISO 4217 currency code string - pub currency_code: String, - /// Minimum amount of currency that the offer is valid for - pub min: Option, - /// Maximum amount of currency that the offer is valid for - pub max: Option, - /// A list of payment methods to select from - pub methods: Vec, -} - -/// Struct for [Offering's PayinMethod](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#payinmethod) -#[derive(Debug, Deserialize, PartialEq, Serialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct PayoutMethod { - /// Unique string identifying a single kind of payment method i.e. (i.e. DEBIT_CARD, BITCOIN_ADDRESS, SQUARE_PAY) - pub kind: String, - /// Estimated time taken to settle an order, expressed in seconds - pub estimated_settlement_time: u64, - /// Payment Method name. Expected to be rendered on screen. - pub name: Option, - /// Blurb containing helpful information about the payment method. Expected to be rendered on screen. e.g. "segwit addresses only" - pub description: Option, - /// The category for which the given method belongs to - pub group: Option, - /// A JSON Schema containing the fields that need to be collected in the RFQ's selected payment methods in order to use this payment method. - pub required_payment_details: Option, - /// Fee charged to use this payment method. absence of this field implies that there is no additional fee associated to the respective payment method - pub fee: Option, - /// Minimum amount required to use this payment method. - pub min: Option, - /// Maximum amount allowed when using this payment method. - pub max: Option, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_data::TestData; - - #[test] - fn can_create() { - let offering = Offering::create(CreateOptions { - from: "did:example:1234".to_string(), - data: OfferingData { - description: "my fake offering".to_string(), - payout_units_per_payin_unit: "2".to_string(), - payin: PayinDetails { - currency_code: "USD".to_string(), - ..Default::default() - }, - payout: PayoutDetails { - currency_code: "BTC".to_string(), - ..Default::default() - }, - required_claims: PresentationDefinition::default(), - }, - ..Default::default() - }) - .expect("failed to create offering"); - - assert_eq!(offering.metadata.id.type_prefix(), "offering"); - assert_eq!(offering.metadata.from, "did:example:1234".to_string()); - assert_eq!(offering.metadata.protocol, "1.0".to_string()); - assert_eq!(offering.data.description, "my fake offering"); - } - - #[test] - fn can_parse_offering_from_json() { - let offering = TestData::get_offering("did:example:1234".to_string()); - let json = serde_json::to_string(&offering).expect("failed to serialize offering"); - let parsed_offering: Resource = - serde_json::from_str(&json).expect("failed to deserialize offering"); - - assert_eq!(offering, parsed_offering) - } -} diff --git a/crates/protocol/src/test_data.rs b/crates/protocol/src/test_data.rs deleted file mode 100644 index 324aa225..00000000 --- a/crates/protocol/src/test_data.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::messages::close::{Close, CloseData}; -use crate::messages::quote::{ - PaymentInstruction, PaymentInstructions, Quote, QuoteData, QuoteDetails, -}; -use crate::messages::Message; -use crate::resources::offering::{ - CreateOptions, Offering, OfferingData, PayinDetails, PayoutDetails, -}; -use crate::resources::Resource; -use chrono::Utc; -use credentials::pex::v2::{Constraints, Field, InputDescriptor, PresentationDefinition}; -use serde_json::{json, Value as JsonValue}; -use type_safe_id::{DynamicType, TypeSafeId}; - -#[cfg(test)] -pub struct TestData; - -#[cfg(test)] -impl TestData { - pub fn get_offering(from: String) -> Resource { - Offering::create(CreateOptions { - from, - data: OfferingData { - description: "my fake offering".to_string(), - payout_units_per_payin_unit: "2".to_string(), - payin: PayinDetails { - currency_code: "USD".to_string(), - ..Default::default() - }, - payout: PayoutDetails { - currency_code: "BTC".to_string(), - ..Default::default() - }, - required_claims: TestData::get_presentation_definition(), - }, - ..Default::default() - }) - .expect("failed to create offering") - } - - pub fn get_close(from: String, exchange_id: TypeSafeId) -> Message { - Close::create( - from, - "did:example:to_1234".to_string(), - exchange_id, - Some("I don't want to do business with you anymore".to_string()), - ) - .expect("failed to create Close") - } - - pub fn get_quote( - from: String, - to: String, - exchange_id: TypeSafeId, - ) -> Message { - Quote::create( - from, - to, - exchange_id, - QuoteData { - expires_at: Utc::now(), - payin: QuoteDetails { - currency_code: "USD".to_string(), - amount_subunits: "100".to_string(), - fee_subunits: Some("10".to_string()), - }, - payout: QuoteDetails { - currency_code: "BTC".to_string(), - amount_subunits: "2500".to_string(), - fee_subunits: None, - }, - payment_instructions: Some(PaymentInstructions { - payin: Some(PaymentInstruction { - link: Some("example.com/payin".to_string()), - instruction: Some("Hand me the cash".to_string()), - }), - payout: Some(PaymentInstruction { - link: None, - instruction: Some("BOLT 12".to_string()), - }), - }), - }, - ) - .expect("failed to create Quote") - } - - fn get_presentation_definition() -> PresentationDefinition { - PresentationDefinition { - id: "test-pd-id".to_string(), - name: Some("simple PD".to_string()), - purpose: Some("pd for testing".to_string()), - input_descriptors: vec![TestData::get_input_descriptor()], - ..Default::default() - } - } - - fn get_input_descriptor() -> InputDescriptor { - InputDescriptor { - id: "whatever".to_string(), - purpose: Some("id for testing".to_string()), - constraints: Constraints { - fields: Some(vec![Field { - path: vec!["$.credentialSubject.btcAddress".to_string()], - ..Default::default() - }]), - ..Default::default() - }, - ..Default::default() - } - } - - pub fn required_payment_details_schema() -> JsonValue { - json! { - r#" - { - "${'$'}schema": "http://json-schema.org/draft-07/schema", - "additionalProperties": false, - "type": "object", - "properties": { - "phoneNumber": { - "minLength": 12, - "pattern": "^+2547[0-9]{8}${'$'}", - "description": "Mobile Money account number of the Recipient", - "type": "string", - "title": "Phone Number", - "maxLength": 12 - }, - "accountHolderName": { - "pattern": "^[A-Za-zs'-]+${'$'}", - "description": "Name of the account holder as it appears on the Mobile Money account", - "type": "string", - "title": "Account Holder Name", - "maxLength": 32 - } - }, - "required": [ - "accountNumber", - "accountHolderName" - ] - } - "# - } - } -} diff --git a/crates/tbdex/Cargo.toml b/crates/tbdex/Cargo.toml index 161f0f54..ecb345d7 100644 --- a/crates/tbdex/Cargo.toml +++ b/crates/tbdex/Cargo.toml @@ -7,6 +7,16 @@ repository.workspace = true license-file.workspace = true [dependencies] -thiserror = { workspace = true } +base64 = "0.22.0" +chrono = "0.4.38" +josekit = "0.8.6" +rand = "0.8.5" +reqwest = { version = "0.12.5", features = ["blocking"] } +sha2 = "0.10.8" serde = { workspace = true } -web5 = { workspace = true } +serde_jcs = "0.1.0" +serde_json = { workspace = true } +thiserror = { workspace = true } +type-safe-id = { version = "0.3.0", features = ["serde"] } +uuid = "1.9.0" +web5 = { workspace = true } \ No newline at end of file diff --git a/crates/tbdex/src/http_client/balances.rs b/crates/tbdex/src/http_client/balances.rs new file mode 100644 index 00000000..eb56c8bf --- /dev/null +++ b/crates/tbdex/src/http_client/balances.rs @@ -0,0 +1,32 @@ +use super::{generate_access_token, Result}; +use crate::resources::balance::Balance; +use reqwest::blocking::Client; +use serde::Deserialize; +use web5::apid::dids::bearer_did::BearerDid; + +#[derive(Deserialize)] +struct GetBalancesResponse { + data: Vec, +} + +pub fn get_balances(pfi_did_uri: &str, bearer_did: &BearerDid) -> Result> { + // TODO resolve pfi did for service endpoint; waiting on did:dht resolution + let endpoint = "http://localhost:9000/balance"; + // TODO the above + + let access_token = generate_access_token(pfi_did_uri, bearer_did)?; + + let response = Client::new() + .get(endpoint) + .bearer_auth(access_token) + .send()? + .text()?; + + let balances_response: GetBalancesResponse = serde_json::from_str(&response)?; + // TODO uncomment with did:dht resolution support + // for balance in &balances_response.data { + // balance.verify()?; + // } + + Ok(balances_response.data) +} diff --git a/crates/tbdex/src/http_client/exchanges.rs b/crates/tbdex/src/http_client/exchanges.rs new file mode 100644 index 00000000..b5799ac2 --- /dev/null +++ b/crates/tbdex/src/http_client/exchanges.rs @@ -0,0 +1,143 @@ +use super::Result; +use crate::{ + http_client::{generate_access_token, HttpClientError}, + messages::{ + close::Close, order::Order, order_status::OrderStatus, quote::Quote, rfq::Rfq, MessageKind, + }, +}; +use reqwest::blocking::Client; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use web5::apid::dids::bearer_did::BearerDid; + +#[derive(Clone, Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Exchange { + pub rfq: Rfq, + #[serde(skip_serializing_if = "Option::is_none")] + pub quote: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub order: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub order_statuses: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub close: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "lowercase")] +struct CreateExchangeRequest { + rfq: Rfq, + #[serde(skip_serializing_if = "Option::is_none")] + reply_to: Option, +} + +pub fn create_exchange(rfq: &Rfq, reply_to: Option) -> Result<()> { + // TODO resolve pfi did for service endpoint; waiting on did:dht resolution + let endpoint = "http://localhost:9000/exchanges"; + // TODO the above + + // TODO uncomment with did:dht resolution support + // rfq.verify()?; + + let request_body = serde_json::to_string(&CreateExchangeRequest { + rfq: rfq.clone(), + reply_to, + })?; + + // todo handle error responses response.status() and response.text() + let _response = Client::new() + .post(endpoint) + .header("Content-Type", "application/json") + .body(request_body) + .send()?; + + Ok(()) +} + +pub fn submit_order(order: &Order) -> Result<()> { + // TODO resolve pfi did for service endpoint; waiting on did:dht resolution + let endpoint = format!( + "http://localhost:9000/exchanges/{}", + order.metadata.exchange_id + ); + // TODO the above + + // TODO uncomment with did:dht resolution support + // order.verify()?; + + let request_body = serde_json::to_string(&order)?; + // todo handle error responses response.status() and response.text() + let _response = Client::new() + .put(endpoint) + .header("Content-Type", "application/json") + .body(request_body) + .send()?; + + Ok(()) +} + +pub fn submit_close(_close: &Close) -> Result<()> { + println!("TbdexHttpClient::submit_close() invoked"); + Ok(()) +} + +#[derive(Deserialize)] +struct GetExchangeResponse { + data: Vec, +} + +pub fn get_exchange( + pfi_did_uri: &str, + bearer_did: &BearerDid, + exchange_id: &str, +) -> Result { + // TODO resolve pfi did for service endpoint; waiting on did:dht resolution + let endpoint = format!("http://localhost:9000/exchanges/{}", exchange_id); + // TODO the above + + let access_token = generate_access_token(pfi_did_uri, bearer_did)?; + + let response = Client::new() + .get(endpoint) + .bearer_auth(access_token) + .send()? + .text()?; + + // TODO handle error response + + let mut exchange = Exchange::default(); + + // TODO cryptographic verifications + + let data = serde_json::from_str::(&response)?.data; + for message in data { + let kind = message + .get("metadata") + .and_then(|m| m.get("kind")) + .and_then(|k| k.as_str()) + .ok_or(HttpClientError::ExchangeMapping)?; + + match MessageKind::from_str(kind)? { + MessageKind::Rfq => exchange.rfq = serde_json::from_value(message)?, + MessageKind::Quote => exchange.quote = Some(serde_json::from_value(message)?), + MessageKind::Order => exchange.order = Some(serde_json::from_value(message)?), + MessageKind::OrderStatus => { + let order_status = serde_json::from_value::(message)?; + if let Some(order_statuses) = &mut exchange.order_statuses { + order_statuses.push(order_status); + } else { + exchange.order_statuses = Some(vec![order_status]); + } + } + MessageKind::Close => exchange.close = Some(serde_json::from_value(message)?), + } + } + + Ok(exchange) +} + +pub fn get_exchanges(_pfi_did: &str, _requestor_did: &BearerDid) -> Result> { + println!("TbdexHttpClient::get_exchanges() invoked"); + Ok(vec![]) +} diff --git a/crates/tbdex/src/http_client/mod.rs b/crates/tbdex/src/http_client/mod.rs index b46704c6..c31cdb57 100644 --- a/crates/tbdex/src/http_client/mod.rs +++ b/crates/tbdex/src/http_client/mod.rs @@ -1,59 +1,73 @@ -use std::sync::Arc; +pub mod balances; +pub mod exchanges; +pub mod offerings; -use web5::apid::dids::bearer_did::BearerDid; - -use crate::{ - messages::{close::Close, order::Order, rfq::Rfq, Message}, - resources::{balance::Balance, offering::Offering}, -}; +use crate::{jose::Signer, messages::MessageError, resources::ResourceError}; +use josekit::{jwt::JwtPayload, JoseError as JosekitError}; +use reqwest::Error as ReqwestError; +use serde_json::Error as SerdeJsonError; +use std::time::{Duration, SystemTime}; +use uuid::Uuid; +use web5::apid::dids::bearer_did::{BearerDid, BearerDidError}; #[derive(thiserror::Error, Debug, Clone, PartialEq)] -pub enum TbdexHttpClientError { - #[error("unknown -- temporarily stubbed in")] - UnknownError, +pub enum HttpClientError { + #[error("reqwest error {0}")] + ReqwestError(String), + #[error("serde json error {0}")] + SerdeJson(String), + #[error(transparent)] + BearerDid(#[from] BearerDidError), + #[error("jose error {0}")] + Jose(String), + #[error(transparent)] + Resource(#[from] ResourceError), + #[error(transparent)] + Message(#[from] MessageError), + #[error("unable to map response to exchange")] + ExchangeMapping, } -type Result = std::result::Result; - -pub struct TbdexHttpClient; - -impl TbdexHttpClient { - pub fn get_offerings(_pfi_did: String) -> Result> { - println!("TbdexHttpClient::get_offerings() invoked"); - Ok(vec![]) +impl From for HttpClientError { + fn from(err: ReqwestError) -> Self { + HttpClientError::ReqwestError(err.to_string()) } +} - pub fn get_balances(_pfi_did: String, _requestor_did: BearerDid) -> Result> { - println!("TbdexHttpClient::get_balances() invoked"); - Ok(vec![]) +impl From for HttpClientError { + fn from(err: SerdeJsonError) -> Self { + HttpClientError::SerdeJson(err.to_string()) } +} - pub fn create_exchange(_rfq: Rfq, _reply_to: Option) -> Result<()> { - println!("TbdexHttpClient::create_exchange() invoked"); - Ok(()) +impl From for HttpClientError { + fn from(err: JosekitError) -> Self { + HttpClientError::Jose(err.to_string()) } +} - pub fn submit_order(_order: Order) -> Result<()> { - println!("TbdexHttpClient::submit_order() invoked"); - Ok(()) - } +type Result = std::result::Result; - pub fn submit_close(_close: Close) -> Result<()> { - println!("TbdexHttpClient::submit_close() invoked"); - Ok(()) - } +fn generate_access_token(pfi_did_uri: &str, bearer_did: &BearerDid) -> Result { + let now = SystemTime::now(); + let exp = now + Duration::from_secs(60); - pub fn get_exchange( - _pfi_did: String, - _requestor_did: BearerDid, - _exchange_id: String, - ) -> Result>> { - println!("TbdexHttpClient::get_exchange() invoked"); - Ok(vec![]) - } + let mut payload = JwtPayload::new(); + payload.set_audience(vec![pfi_did_uri]); + payload.set_issuer(&bearer_did.did.uri); + payload.set_issued_at(&now); + payload.set_expires_at(&exp); + payload.set_jwt_id(Uuid::new_v4().to_string()); - pub fn get_exchanges(_pfi_did: String, _requestor_did: BearerDid) -> Result> { - println!("TbdexHttpClient::get_exchanges() invoked"); - Ok(vec![]) - } + // default to first VM + let key_id = bearer_did.document.verification_method[0].id.clone(); + let web5_signer = bearer_did.get_signer(key_id.clone())?; + let jose_signer = Signer { + kid: key_id, + web5_signer, + }; + + let access_token = jose_signer.sign_jwt(&payload)?; + + Ok(access_token) } diff --git a/crates/tbdex/src/http_client/offerings.rs b/crates/tbdex/src/http_client/offerings.rs new file mode 100644 index 00000000..3be695e8 --- /dev/null +++ b/crates/tbdex/src/http_client/offerings.rs @@ -0,0 +1,27 @@ +use super::Result; +use crate::resources::offering::Offering; +use reqwest::blocking::get; +use serde::Deserialize; + +#[derive(Deserialize)] +struct GetOfferingsResponse { + data: Vec, +} + +pub fn get_offerings(_pfi_did_uri: &str) -> Result> { + // TODO resolve pfi did for service endpoint; waiting on did:dht resolution + let endpoint = "http://localhost:9000/offerings"; + // TODO the above + + let response = get(endpoint)?.text()?; + + // TODO handle error response + + let offerings_response = serde_json::from_str::(&response)?; + // TODO uncomment with did:dht resolution support + // for offering in &offerings_response.data { + // offering.verify()?; + // } + + Ok(offerings_response.data) +} diff --git a/crates/tbdex/src/jose.rs b/crates/tbdex/src/jose.rs new file mode 100644 index 00000000..edd01fe1 --- /dev/null +++ b/crates/tbdex/src/jose.rs @@ -0,0 +1,148 @@ +use crate::signature::SignatureError; +use josekit::{ + jws::{ + alg::eddsa::EddsaJwsAlgorithm, serialize_compact, JwsAlgorithm, JwsHeader, JwsSigner, + JwsVerifier, + }, + jwt::{encode_with_signer, JwtPayload}, + JoseError as JosekitError, +}; +use std::{ + collections::HashMap, + fmt::{Debug, Formatter}, + sync::Arc, +}; +use web5::apid::{ + crypto::jwk::Jwk, + dids::data_model::document::Document, + dsa::{ed25519::Ed25519Verifier, DsaError, Signer as Web5Signer, Verifier as Web5Verifier}, +}; + +#[derive(Clone)] +pub struct Signer { + pub kid: String, + pub web5_signer: Arc, +} + +impl JwsSigner for Signer { + fn algorithm(&self) -> &dyn JwsAlgorithm { + &EddsaJwsAlgorithm::Eddsa + } + + fn key_id(&self) -> Option<&str> { + Some(&self.kid) + } + + fn signature_len(&self) -> usize { + 64 + } + + fn sign(&self, message: &[u8]) -> core::result::Result, JosekitError> { + self.web5_signer + .sign(message) + // 🚧 improve error message semantics + .map_err(|err| JosekitError::InvalidSignature(err.into())) + } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Debug for Signer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Signer").field("kid", &self.kid).finish() + } +} + +impl Signer { + pub fn sign_detached_compact_jws(&self, payload: &[u8]) -> Result { + let compact_jws = serialize_compact(payload, &JwsHeader::new(), self)?; + let parts: Vec<&str> = compact_jws.split('.').collect(); + let detached_compact_jws = format!("{}..{}", parts[0], parts[2]); + Ok(detached_compact_jws) + } + + pub fn sign_jwt(&self, payload: &JwtPayload) -> Result { + let mut header = JwsHeader::new(); + header.set_token_type("JWT"); + encode_with_signer(payload, &header, self) + } +} + +#[derive(Clone, Debug)] +pub struct Verifier { + pub kid: String, + pub public_jwk: Jwk, +} + +impl JwsVerifier for Verifier { + fn algorithm(&self) -> &dyn JwsAlgorithm { + &EddsaJwsAlgorithm::Eddsa + } + + fn key_id(&self) -> Option<&str> { + Some(self.kid.as_str()) + } + + fn verify(&self, message: &[u8], signature: &[u8]) -> core::result::Result<(), JosekitError> { + let verifier = Ed25519Verifier::new(self.public_jwk.clone()); + let result = verifier + .verify(message, signature) + .map_err(|e| JosekitError::InvalidSignature(e.into()))?; + + match result { + true => Ok(()), + false => Err(JosekitError::InvalidSignature( + // 🚧 improve error message semantics + DsaError::VerificationFailure("ed25519 verification failed".to_string()).into(), + )), + } + } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +fn create_selector<'a>( + verifiers: &'a HashMap>, +) -> impl Fn(&JwsHeader) -> core::result::Result, JosekitError> + 'a { + move |header: &JwsHeader| -> core::result::Result, JosekitError> { + let kid = header.key_id().ok_or_else(|| { + JosekitError::InvalidJwsFormat(SignatureError::Jose("missing kid".to_string()).into()) + })?; + + let verifier = verifiers.get(kid).ok_or_else(|| { + JosekitError::InvalidJwsFormat( + SignatureError::Jose("verification method not found".to_string()).into(), + ) + })?; + + Ok(Some(&**verifier)) + } +} + +impl Verifier { + pub fn verify_compact_jws(document: Document, message: String) -> Result<(), JosekitError> { + let verifiers: HashMap> = document + .verification_method + .iter() + .map(|method| { + ( + method.id.clone(), + Arc::new(Verifier { + kid: method.id.clone(), + public_jwk: method.public_key_jwk.clone(), + }), + ) + }) + .collect(); + + let selector = create_selector(&verifiers); + + josekit::jws::deserialize_compact_with_selector(message, selector)?; + + Ok(()) + } +} diff --git a/crates/tbdex/src/lib.rs b/crates/tbdex/src/lib.rs index 8ddb8063..bdf36862 100644 --- a/crates/tbdex/src/lib.rs +++ b/crates/tbdex/src/lib.rs @@ -1,3 +1,6 @@ pub mod http_client; pub mod messages; pub mod resources; + +mod jose; +mod signature; diff --git a/crates/tbdex/src/messages/close.rs b/crates/tbdex/src/messages/close.rs index bf0376cc..58cb35f5 100644 --- a/crates/tbdex/src/messages/close.rs +++ b/crates/tbdex/src/messages/close.rs @@ -1,7 +1,9 @@ -use super::{Message, MessageKind, MessageMetadata, Result}; +use super::{MessageKind, MessageMetadata, Result}; +use chrono::Utc; +use serde::{Deserialize, Serialize}; use web5::apid::dids::bearer_did::BearerDid; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Close { pub metadata: MessageMetadata, pub data: CloseData, @@ -10,45 +12,60 @@ pub struct Close { impl Close { pub fn new( - to: String, - from: String, - exchange_id: String, - data: CloseData, - protocol: String, + bearer_did: &BearerDid, + to: &str, + from: &str, + exchange_id: &str, + data: &CloseData, + protocol: &str, external_id: Option, - ) -> Self { - // 🚧 not functional - Self { - metadata: MessageMetadata { - from, - to, - kind: MessageKind::Order, - id: String::default(), - exchange_id, - external_id, - protocol, - created_at: String::default(), - }, - data, - signature: String::default(), - } + ) -> Result { + let metadata = MessageMetadata { + from: from.to_string(), + to: to.to_string(), + kind: MessageKind::Close, + id: MessageKind::Close.typesafe_id()?, + exchange_id: exchange_id.to_string(), + external_id, + protocol: protocol.to_string(), + created_at: Utc::now().to_rfc3339(), + }; + + Ok(Self { + metadata: metadata.clone(), + data: data.clone(), + signature: crate::signature::sign( + bearer_did, + &serde_json::to_value(metadata)?, + &serde_json::to_value(data)?, + )?, + }) + } + + pub fn from_json_string(json: &str) -> Result { + let close = serde_json::from_str::(json)?; + close.verify()?; + Ok(close) } -} -impl Message for Close { - fn sign(&self, _bearer_did: BearerDid) -> Result<()> { - println!("Order.sign() invoked"); - Ok(()) + pub fn verify(&self) -> Result<()> { + Ok(crate::signature::verify( + &self.metadata.from, + &serde_json::to_value(self.metadata.clone())?, + &serde_json::to_value(self.data.clone())?, + &self.signature, + )?) } - fn verify(&self) -> Result<()> { - println!("Order.verify() invoked"); - Ok(()) + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self)?) } } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct CloseData { + #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub success: Option, } diff --git a/crates/tbdex/src/messages/mod.rs b/crates/tbdex/src/messages/mod.rs index d82a0917..1d6dc78d 100644 --- a/crates/tbdex/src/messages/mod.rs +++ b/crates/tbdex/src/messages/mod.rs @@ -4,11 +4,47 @@ pub mod order_status; pub mod quote; pub mod rfq; +use crate::signature::SignatureError; use serde::{Deserialize, Serialize}; -use web5::apid::dids::bearer_did::BearerDid; +use serde_json::Error as SerdeJsonError; +use std::str::FromStr; +use type_safe_id::{DynamicType, Error as TypeIdError, TypeSafeId}; +use web5::apid::dids::bearer_did::BearerDidError; -#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] +#[derive(thiserror::Error, Debug, Clone, PartialEq)] +pub enum MessageError { + #[error("serde json error {0}")] + SerdeJson(String), + #[error("typeid error {0}")] + TypeId(String), + #[error(transparent)] + BearerDid(#[from] BearerDidError), + #[error(transparent)] + Signature(#[from] SignatureError), + #[error("unknown kind {0}")] + UnknownKind(String), + #[error("offering verification failure {0}")] + OfferingVerification(String), +} + +impl From for MessageError { + fn from(err: SerdeJsonError) -> Self { + MessageError::SerdeJson(err.to_string()) + } +} + +impl From for MessageError { + fn from(err: TypeIdError) -> Self { + MessageError::TypeId(err.to_string()) + } +} + +type Result = std::result::Result; + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize, Clone)] +#[serde(rename_all = "lowercase")] pub enum MessageKind { + #[default] Rfq, Quote, Order, @@ -16,7 +52,30 @@ pub enum MessageKind { Close, } -#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] +impl MessageKind { + pub fn typesafe_id(&self) -> Result { + let serialized_kind = serde_json::to_string(&self)?; + let dynamic_type = DynamicType::new(serialized_kind.trim_matches('"'))?; + Ok(TypeSafeId::new_with_type(dynamic_type).to_string()) + } +} + +impl FromStr for MessageKind { + type Err = MessageError; + + fn from_str(s: &str) -> Result { + match s { + "rfq" => Ok(MessageKind::Rfq), + "quote" => Ok(MessageKind::Quote), + "order" => Ok(MessageKind::Order), + "orderstatus" => Ok(MessageKind::OrderStatus), + "close" => Ok(MessageKind::Close), + _ => Err(MessageError::UnknownKind(s.to_string())), + } + } +} + +#[derive(Debug, Deserialize, Default, PartialEq, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct MessageMetadata { pub from: String, @@ -24,20 +83,8 @@ pub struct MessageMetadata { pub kind: MessageKind, pub id: String, pub exchange_id: String, + #[serde(skip_serializing_if = "Option::is_none")] pub external_id: Option, pub protocol: String, pub created_at: String, } - -#[derive(thiserror::Error, Debug, Clone, PartialEq)] -pub enum MessageError { - #[error("unknown -- temporarily stubbed in")] - UnknownError, -} - -type Result = std::result::Result; - -pub trait Message: Send + Sync { - fn sign(&self, bearer_did: BearerDid) -> Result<()>; - fn verify(&self) -> Result<()>; -} diff --git a/crates/tbdex/src/messages/order.rs b/crates/tbdex/src/messages/order.rs index 7f6473fa..bd359b71 100644 --- a/crates/tbdex/src/messages/order.rs +++ b/crates/tbdex/src/messages/order.rs @@ -1,45 +1,67 @@ -use super::{Message, MessageKind, MessageMetadata, Result}; +use super::{MessageKind, MessageMetadata, Result}; +use chrono::Utc; +use serde::{Deserialize, Serialize}; use web5::apid::dids::bearer_did::BearerDid; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Order { pub metadata: MessageMetadata, + pub data: OrderData, pub signature: String, } impl Order { pub fn new( - to: String, - from: String, - exchange_id: String, - protocol: String, + bearer_did: &BearerDid, + to: &str, + from: &str, + exchange_id: &str, + protocol: &str, external_id: Option, - ) -> Self { - // 🚧 not functional - Self { - metadata: MessageMetadata { - from, - to, - kind: MessageKind::Order, - id: String::default(), - exchange_id, - external_id, - protocol, - created_at: String::default(), - }, - signature: String::default(), - } + ) -> Result { + let metadata = MessageMetadata { + from: from.to_string(), + to: to.to_string(), + kind: MessageKind::Order, + id: MessageKind::Order.typesafe_id()?, + exchange_id: exchange_id.to_string(), + external_id, + protocol: protocol.to_string(), + created_at: Utc::now().to_rfc3339(), + }; + + let data = OrderData {}; + + Ok(Self { + metadata: metadata.clone(), + data: data.clone(), + signature: crate::signature::sign( + bearer_did, + &serde_json::to_value(metadata)?, + &serde_json::to_value(&data)?, + )?, + }) } -} -impl Message for Order { - fn sign(&self, _bearer_did: BearerDid) -> Result<()> { - println!("Order.sign() invoked"); - Ok(()) + pub fn from_json_string(json: &str) -> Result { + let order = serde_json::from_str::(json)?; + order.verify()?; + Ok(order) } - fn verify(&self) -> Result<()> { - println!("Order.verify() invoked"); - Ok(()) + pub fn verify(&self) -> Result<()> { + Ok(crate::signature::verify( + &self.metadata.from, + &serde_json::to_value(self.metadata.clone())?, + &serde_json::to_value(&OrderData {})?, + &self.signature, + )?) + } + + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self)?) } } + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +pub struct OrderData {} diff --git a/crates/tbdex/src/messages/order_status.rs b/crates/tbdex/src/messages/order_status.rs index 225775e2..e3f6b888 100644 --- a/crates/tbdex/src/messages/order_status.rs +++ b/crates/tbdex/src/messages/order_status.rs @@ -1,7 +1,9 @@ -use super::{Message, MessageKind, MessageMetadata, Result}; +use super::{MessageKind, MessageMetadata, Result}; +use chrono::Utc; +use serde::{Deserialize, Serialize}; use web5::apid::dids::bearer_did::BearerDid; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct OrderStatus { pub metadata: MessageMetadata, pub data: OrderStatusData, @@ -10,44 +12,58 @@ pub struct OrderStatus { impl OrderStatus { pub fn new( - to: String, - from: String, - exchange_id: String, - data: OrderStatusData, - protocol: String, + bearer_did: &BearerDid, + to: &str, + from: &str, + exchange_id: &str, + data: &OrderStatusData, + protocol: &str, external_id: Option, - ) -> Self { - // 🚧 not functional - Self { - metadata: MessageMetadata { - from, - to, - kind: MessageKind::Order, - id: String::default(), - exchange_id, - external_id, - protocol, - created_at: String::default(), - }, - data, - signature: String::default(), - } + ) -> Result { + let metadata = MessageMetadata { + from: from.to_string(), + to: to.to_string(), + kind: MessageKind::OrderStatus, + id: MessageKind::OrderStatus.typesafe_id()?, + exchange_id: exchange_id.to_string(), + external_id, + protocol: protocol.to_string(), + created_at: Utc::now().to_rfc3339(), + }; + + Ok(Self { + metadata: metadata.clone(), + data: data.clone(), + signature: crate::signature::sign( + bearer_did, + &serde_json::to_value(metadata)?, + &serde_json::to_value(data)?, + )?, + }) + } + + pub fn from_json_string(json: &str) -> Result { + let order_status = serde_json::from_str::(json)?; + order_status.verify()?; + Ok(order_status) } -} -impl Message for OrderStatus { - fn sign(&self, _bearer_did: BearerDid) -> Result<()> { - println!("Order.sign() invoked"); - Ok(()) + pub fn verify(&self) -> Result<()> { + Ok(crate::signature::verify( + &self.metadata.from, + &serde_json::to_value(self.metadata.clone())?, + &serde_json::to_value(self.data.clone())?, + &self.signature, + )?) } - fn verify(&self) -> Result<()> { - println!("Order.verify() invoked"); - Ok(()) + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self)?) } } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct OrderStatusData { pub order_status: String, } diff --git a/crates/tbdex/src/messages/quote.rs b/crates/tbdex/src/messages/quote.rs index 2b4c2f04..3666c805 100644 --- a/crates/tbdex/src/messages/quote.rs +++ b/crates/tbdex/src/messages/quote.rs @@ -1,7 +1,9 @@ -use super::{Message, MessageKind, MessageMetadata, Result}; +use super::{MessageKind, MessageMetadata, Result}; +use chrono::Utc; +use serde::{Deserialize, Serialize}; use web5::apid::dids::bearer_did::BearerDid; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Quote { pub metadata: MessageMetadata, pub data: QuoteData, @@ -10,60 +12,80 @@ pub struct Quote { impl Quote { pub fn new( - to: String, - from: String, - exchange_id: String, - data: QuoteData, - protocol: String, + bearer_did: &BearerDid, + to: &str, + from: &str, + exchange_id: &str, + data: &QuoteData, + protocol: &str, external_id: Option, - ) -> Self { - // 🚧 not functional - Self { - metadata: MessageMetadata { - from, - to, - kind: MessageKind::Quote, - id: String::default(), - exchange_id, - external_id, - protocol, - created_at: String::default(), - }, - data, - signature: String::default(), - } + ) -> Result { + let metadata = MessageMetadata { + from: from.to_string(), + to: to.to_string(), + kind: MessageKind::Quote, + id: MessageKind::Quote.typesafe_id()?, + exchange_id: exchange_id.to_string(), + external_id, + protocol: protocol.to_string(), + created_at: Utc::now().to_rfc3339(), + }; + + Ok(Self { + metadata: metadata.clone(), + data: data.clone(), + signature: crate::signature::sign( + bearer_did, + &serde_json::to_value(metadata)?, + &serde_json::to_value(data)?, + )?, + }) + } + + pub fn from_json_string(json: &str) -> Result { + let quote = serde_json::from_str::(json)?; + quote.verify()?; + Ok(quote) } -} -impl Message for Quote { - fn sign(&self, _bearer_did: BearerDid) -> Result<()> { - println!("Quote.sign() invoked"); - Ok(()) + pub fn verify(&self) -> Result<()> { + Ok(crate::signature::verify( + &self.metadata.from, + &serde_json::to_value(self.metadata.clone())?, + &serde_json::to_value(self.data.clone())?, + &self.signature, + )?) } - fn verify(&self) -> Result<()> { - println!("Quote.verify() invoked"); - Ok(()) + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self)?) } } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct QuoteData { pub expires_at: String, pub payin: QuoteDetails, pub payout: QuoteDetails, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct QuoteDetails { pub currency_code: String, pub amount: String, + #[serde(skip_serializing_if = "Option::is_none")] pub fee: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub payment_instructions: Option, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct PaymentInstructions { + #[serde(skip_serializing_if = "Option::is_none")] pub link: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub instruction: Option, } diff --git a/crates/tbdex/src/messages/rfq.rs b/crates/tbdex/src/messages/rfq.rs index a8815f3b..240bd833 100644 --- a/crates/tbdex/src/messages/rfq.rs +++ b/crates/tbdex/src/messages/rfq.rs @@ -1,8 +1,14 @@ -use super::{Message, MessageKind, MessageMetadata, Result}; -use crate::resources::offering::Offering; +use super::{MessageKind, MessageMetadata, Result}; +use crate::{messages::MessageError, resources::offering::Offering}; +use base64::{engine::general_purpose, Engine as _}; +use chrono::Utc; +use rand::{rngs::OsRng, RngCore}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use web5::apid::dids::bearer_did::BearerDid; -#[derive(Clone)] +#[derive(Clone, Serialize, Default, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct Rfq { pub metadata: MessageMetadata, pub data: RfqData, @@ -12,49 +18,113 @@ pub struct Rfq { impl Rfq { pub fn new( - to: String, - from: String, - _create_rfq_data: CreateRfqData, - protocol: String, + bearer_did: &BearerDid, + to: &str, + from: &str, + create_rfq_data: &CreateRfqData, + protocol: &str, external_id: Option, - ) -> Self { - // 🚧 not functional - Self { - metadata: MessageMetadata { - from, - to, - kind: MessageKind::Rfq, - id: String::default(), - exchange_id: String::default(), - external_id, - protocol, - created_at: String::default(), - }, - data: RfqData { - offering_id: String::default(), - payin: SelectedPayinMethod { - kind: String::default(), - payment_details_hash: None, - amount: String::default(), - }, - payout: SelectedPayoutMethod { - kind: String::default(), - payment_details_hash: None, - }, - claims_hash: None, - }, - private_data: RfqPrivateData { - salt: String::default(), - payin: None, - payout: None, - claims: None, - }, - signature: String::default(), - } + ) -> Result { + let id = MessageKind::Rfq.typesafe_id()?; + + let metadata = MessageMetadata { + from: from.to_string(), + to: to.to_string(), + kind: MessageKind::Rfq, + id: id.clone(), + exchange_id: id.clone(), + external_id, + protocol: protocol.to_string(), + created_at: Utc::now().to_rfc3339(), + }; + + let (data, private_data) = hash_private_data(create_rfq_data); + + Ok(Self { + metadata: metadata.clone(), + data: data.clone(), + private_data, + signature: crate::signature::sign( + bearer_did, + &serde_json::to_value(metadata)?, + &serde_json::to_value(data)?, + )?, + }) + } + + pub fn from_json_string(json: &str) -> Result { + let rfq = serde_json::from_str::(json)?; + rfq.verify()?; + Ok(rfq) + } + + pub fn verify(&self) -> Result<()> { + Ok(crate::signature::verify( + &self.metadata.from, + &serde_json::to_value(self.metadata.clone())?, + &serde_json::to_value(self.data.clone())?, + &self.signature, + )?) + } + + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self)?) } - pub fn verify_offering_requirements(&self, _offering: Offering) -> Result { - println!("Rfq.verify_offering_requirements() invoked"); + pub fn verify_offering_requirements(&self, offering: &Offering) -> Result { + if offering.metadata.protocol != self.metadata.protocol { + return Err(MessageError::OfferingVerification(format!( + "offering has protocol version {} but rfq has protocol version {}", + offering.metadata.protocol, self.metadata.protocol + ))); + } + + if offering.metadata.id != self.data.offering_id { + return Err(MessageError::OfferingVerification(format!( + "offering id is {} but rfq has offering id {}", + offering.metadata.id, self.data.offering_id + ))); + } + + let payin_amount = self.data.payin.amount.parse::().map_err(|_| { + MessageError::OfferingVerification(format!( + "rfq payin amount invalid decimal string {}", + self.data.payin.amount + )) + })?; + + if let Some(max_amount) = offering.data.payin.max.as_ref() { + let max_amount = max_amount.parse::().map_err(|_| { + MessageError::OfferingVerification(format!( + "offering max amount invalid decimal string {}", + max_amount + )) + })?; + + if payin_amount > max_amount { + return Err(MessageError::OfferingVerification(format!( + "rfq payin of {} is larger than max offering amount of {}", + payin_amount, max_amount + ))); + } + } + + if let Some(min_amount) = offering.data.payin.min.as_ref() { + let min_amount = min_amount.parse::().map_err(|_| { + MessageError::OfferingVerification(format!( + "offering min amount invalid decimal string {}", + min_amount + )) + })?; + + if payin_amount < min_amount { + return Err(MessageError::OfferingVerification(format!( + "rfq payin of {} is smaller than min offering amount of {}", + payin_amount, min_amount + ))); + } + } + Ok(true) } @@ -69,18 +139,6 @@ impl Rfq { } } -impl Message for Rfq { - fn sign(&self, _bearer_did: BearerDid) -> Result<()> { - println!("Rfq.sign() invoked"); - Ok(()) - } - - fn verify(&self) -> Result<()> { - println!("Rfq.verify() invoked"); - Ok(()) - } -} - #[derive(Clone)] pub struct CreateRfqData { pub offering_id: String, @@ -92,46 +150,186 @@ pub struct CreateRfqData { #[derive(Clone)] pub struct CreateSelectedPayinMethod { pub kind: String, - pub payment_details: String, // 🚧 Map + pub payment_details: Option, pub amount: String, } #[derive(Clone)] pub struct CreateSelectedPayoutMethod { pub kind: String, - pub payment_details: String, // 🚧 Map + pub payment_details: Option, } -#[derive(Clone)] +#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct RfqData { pub offering_id: String, pub payin: SelectedPayinMethod, pub payout: SelectedPayoutMethod, + #[serde(skip_serializing_if = "Option::is_none")] pub claims_hash: Option, } -#[derive(Clone)] +#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct SelectedPayinMethod { pub kind: String, + #[serde(skip_serializing_if = "Option::is_none")] pub payment_details_hash: Option, pub amount: String, } -#[derive(Clone)] +#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct SelectedPayoutMethod { pub kind: String, + #[serde(skip_serializing_if = "Option::is_none")] pub payment_details_hash: Option, } -#[derive(Clone)] +#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct RfqPrivateData { pub salt: String, + #[serde(skip_serializing_if = "Option::is_none")] pub payin: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub payout: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub claims: Option>, } -#[derive(Clone)] +#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct PrivatePaymentDetails { - pub payment_details: String, // 🚧 Map + #[serde(skip_serializing_if = "Option::is_none")] + pub payment_details: Option, +} + +fn hash_private_data(create_rfq_data: &CreateRfqData) -> (RfqData, RfqPrivateData) { + let salt = generate_random_salt(); + + let payin_payment_details_hash = create_rfq_data + .payin + .payment_details + .as_ref() + .map(|pd| digest_private_data(&salt, pd)); + let payout_payment_details_hash = create_rfq_data + .payout + .payment_details + .as_ref() + .map(|pd| digest_private_data(&salt, pd)); + let claims_hash = if create_rfq_data.claims.is_empty() { + None + } else { + Some(digest_private_data(&salt, &create_rfq_data.claims)) + }; + + let hashed_rfq_data = RfqData { + offering_id: create_rfq_data.offering_id.clone(), + payin: SelectedPayinMethod { + kind: create_rfq_data.payin.kind.clone(), + payment_details_hash: payin_payment_details_hash, + amount: create_rfq_data.payin.amount.clone(), + }, + payout: SelectedPayoutMethod { + kind: create_rfq_data.payout.kind.clone(), + payment_details_hash: payout_payment_details_hash, + }, + claims_hash, + }; + + let private_rfq_data = + RfqPrivateData { + salt: salt.clone(), + payin: create_rfq_data + .payin + .payment_details + .as_ref() + .map(|pd| PrivatePaymentDetails { + payment_details: Some(pd.clone()), + }), + payout: create_rfq_data.payout.payment_details.as_ref().map(|pd| { + PrivatePaymentDetails { + payment_details: Some(pd.clone()), + } + }), + claims: if !create_rfq_data.claims.is_empty() { + Some(create_rfq_data.claims.clone()) + } else { + None + }, + }; + + (hashed_rfq_data, private_rfq_data) +} + +fn generate_random_salt() -> String { + let mut salt = [0u8; 16]; + OsRng.fill_bytes(&mut salt); + general_purpose::URL_SAFE_NO_PAD.encode(salt) +} + +fn digest_private_data(salt: &str, value: &T) -> String { + let digestible = serde_json::json!([salt, value]); + let serialized = serde_json::to_string(&digestible).unwrap(); // 🚧 unwrap! + + let mut hasher = Sha256::new(); + hasher.update(serialized.as_bytes()); + let digest = hasher.finalize(); + + general_purpose::URL_SAFE_NO_PAD.encode(digest) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use web5::apid::{ + crypto::key_managers::in_memory_key_manager::InMemoryKeyManager, + dids::methods::did_jwk::DidJwk, dsa::ed25519::Ed25519Generator, + }; + + #[test] + fn can_create_and_sign() { + let key_manager = InMemoryKeyManager::new(); + let public_jwk = key_manager + .import_private_jwk(Ed25519Generator::generate()) + .unwrap(); + let did_jwk = DidJwk::from_public_jwk(public_jwk).unwrap(); + + let bearer_did = BearerDid::new(&did_jwk.did.uri, Arc::new(key_manager)).unwrap(); + + let rfq = Rfq::new( + &bearer_did, + "did:test:pfi", + &did_jwk.did.uri, + &CreateRfqData { + offering_id: "offering_123".to_string(), + payin: CreateSelectedPayinMethod { + kind: "BTC".to_string(), + payment_details: Some(serde_json::json!({"tmp": "payment-details"})), + amount: "101".to_string(), + }, + payout: CreateSelectedPayoutMethod { + kind: "BTC".to_string(), + payment_details: Some(serde_json::json!({"tmp": "payment-details"})), + }, + claims: vec!["some-claim".to_string()], + }, + "1.0", + None, + ) + .unwrap(); + + assert_ne!(String::default(), rfq.signature); + + let rfq_json_string = rfq.to_json().unwrap(); + + assert_ne!(String::default(), rfq_json_string); + + let parsed_rfq = Rfq::from_json_string(&rfq_json_string).unwrap(); + + assert_eq!(rfq, parsed_rfq); + } } diff --git a/crates/tbdex/src/resources/balance.rs b/crates/tbdex/src/resources/balance.rs index 5613a035..b6a91904 100644 --- a/crates/tbdex/src/resources/balance.rs +++ b/crates/tbdex/src/resources/balance.rs @@ -1,7 +1,9 @@ -use super::{Resource, ResourceKind, ResourceMetadata, Result}; +use super::{ResourceKind, ResourceMetadata, Result}; +use chrono::Utc; +use serde::{Deserialize, Serialize}; use web5::apid::dids::bearer_did::BearerDid; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct Balance { pub metadata: ResourceMetadata, pub data: BalanceData, @@ -9,37 +11,56 @@ pub struct Balance { } impl Balance { - pub fn new(from: String, data: BalanceData, protocol: String) -> Self { - // 🚧 not functional - Self { - metadata: ResourceMetadata { - kind: ResourceKind::Offering, - from, - to: String::default(), - id: String::default(), - protocol, - created_at: String::default(), - updated_at: None, - }, - data, - signature: String::default(), - } + pub fn new( + bearer_did: &BearerDid, + from: &str, + data: &BalanceData, + protocol: &str, + ) -> Result { + let now = Utc::now().to_rfc3339(); + + let metadata = ResourceMetadata { + kind: ResourceKind::Balance, + from: from.to_string(), + id: ResourceKind::Balance.typesafe_id()?, + protocol: protocol.to_string(), + created_at: now.clone(), + updated_at: Some(now), + }; + + Ok(Self { + metadata: metadata.clone(), + data: data.clone(), + signature: crate::signature::sign( + bearer_did, + &serde_json::to_value(metadata)?, + &serde_json::to_value(data)?, + )?, + }) + } + + pub fn from_json_string(json: &str) -> Result { + let balance = serde_json::from_str::(json)?; + balance.verify()?; + Ok(balance) } -} -impl Resource for Balance { - fn sign(&self, _bearer_did: BearerDid) -> Result<()> { - println!("Offering.sign() invoked"); - Ok(()) + pub fn verify(&self) -> Result<()> { + Ok(crate::signature::verify( + &self.metadata.from, + &serde_json::to_value(self.metadata.clone())?, + &serde_json::to_value(self.data.clone())?, + &self.signature, + )?) } - fn verify(&self) -> Result<()> { - println!("Offering.verify() invoked"); - Ok(()) + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self)?) } } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct BalanceData { pub currency_code: String, pub available: String, diff --git a/crates/tbdex/src/resources/mod.rs b/crates/tbdex/src/resources/mod.rs index 94167ca8..a2630d20 100644 --- a/crates/tbdex/src/resources/mod.rs +++ b/crates/tbdex/src/resources/mod.rs @@ -1,36 +1,61 @@ pub mod balance; pub mod offering; +use crate::signature::SignatureError; use serde::{Deserialize, Serialize}; -use web5::apid::dids::bearer_did::BearerDid; +use serde_json::Error as SerdeJsonError; +use type_safe_id::{DynamicType, Error as TypeIdError, TypeSafeId}; +use web5::apid::dids::bearer_did::BearerDidError; + +#[derive(thiserror::Error, Debug, Clone, PartialEq)] +pub enum ResourceError { + #[error("serde json error {0}")] + SerdeJson(String), + #[error("typeid error {0}")] + TypeId(String), + #[error(transparent)] + BearerDid(#[from] BearerDidError), + #[error(transparent)] + Signature(#[from] SignatureError), +} + +impl From for ResourceError { + fn from(err: SerdeJsonError) -> Self { + ResourceError::SerdeJson(err.to_string()) + } +} + +impl From for ResourceError { + fn from(err: TypeIdError) -> Self { + ResourceError::TypeId(err.to_string()) + } +} + +type Result = std::result::Result; #[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] +#[serde(rename_all = "lowercase")] pub enum ResourceKind { Offering, Balance, } +impl ResourceKind { + pub fn typesafe_id(&self) -> Result { + let serialized_kind = serde_json::to_string(&self)?; + let dynamic_type = DynamicType::new(serialized_kind.trim_matches('"'))?; + Ok(TypeSafeId::new_with_type(dynamic_type).to_string()) + } +} + #[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ResourceMetadata { pub kind: ResourceKind, pub from: String, - pub to: String, pub id: String, pub protocol: String, pub created_at: String, + #[serde(skip_serializing_if = "Option::is_none")] pub updated_at: Option, } - -#[derive(thiserror::Error, Debug, Clone, PartialEq)] -pub enum ResourceError { - #[error("unknown -- temporarily stubbed in")] - UnknownError, -} - -type Result = std::result::Result; - -pub trait Resource: Send + Sync { - fn sign(&self, bearer_did: BearerDid) -> Result<()>; - fn verify(&self) -> Result<()>; -} diff --git a/crates/tbdex/src/resources/offering.rs b/crates/tbdex/src/resources/offering.rs index d1aa63e7..6b9b9d47 100644 --- a/crates/tbdex/src/resources/offering.rs +++ b/crates/tbdex/src/resources/offering.rs @@ -1,9 +1,11 @@ -use super::{Resource, ResourceKind, ResourceMetadata, Result}; +use super::{ResourceKind, ResourceMetadata, Result}; +use chrono::Utc; +use serde::{Deserialize, Serialize}; use web5::apid::{ credentials::presentation_definition::PresentationDefinition, dids::bearer_did::BearerDid, }; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Offering { pub metadata: ResourceMetadata, pub data: OfferingData, @@ -11,82 +13,180 @@ pub struct Offering { } impl Offering { - pub fn new(from: String, data: OfferingData, protocol: String) -> Self { - // 🚧 not functional - Self { - metadata: ResourceMetadata { - kind: ResourceKind::Offering, - from, - to: String::default(), - id: String::default(), - protocol, - created_at: String::default(), - updated_at: None, - }, - data, - signature: String::default(), - } + pub fn new( + bearer_did: &BearerDid, + from: &str, + data: &OfferingData, + protocol: &str, + ) -> Result { + let now = Utc::now().to_rfc3339(); + + let metadata = ResourceMetadata { + kind: ResourceKind::Offering, + from: from.to_string(), + id: ResourceKind::Offering.typesafe_id()?, + protocol: protocol.to_string(), + created_at: now.clone(), + updated_at: Some(now), + }; + + Ok(Self { + metadata: metadata.clone(), + data: data.clone(), + signature: crate::signature::sign( + bearer_did, + &serde_json::to_value(metadata)?, + &serde_json::to_value(data)?, + )?, + }) } -} -impl Resource for Offering { - fn sign(&self, _bearer_did: BearerDid) -> Result<()> { - println!("Offering.sign() invoked"); - Ok(()) + pub fn from_json_string(json: &str) -> Result { + let offering = serde_json::from_str::(json)?; + offering.verify()?; + Ok(offering) } - fn verify(&self) -> Result<()> { - println!("Offering.verify() invoked"); - Ok(()) + pub fn verify(&self) -> Result<()> { + Ok(crate::signature::verify( + &self.metadata.from, + &serde_json::to_value(self.metadata.clone())?, + &serde_json::to_value(self.data.clone())?, + &self.signature, + )?) + } + + pub fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self)?) } } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct OfferingData { pub description: String, pub payout_units_per_payin_unit: String, pub payin: PayinDetails, pub payout: PayoutDetails, - pub required_claims: PresentationDefinition, + #[serde(skip_serializing_if = "Option::is_none")] + pub required_claims: Option, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)] +#[serde(rename_all = "camelCase")] pub struct PayinDetails { pub currency_code: String, + #[serde(skip_serializing_if = "Option::is_none")] pub min: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub max: Option, pub methods: Vec, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)] +#[serde(rename_all = "camelCase")] pub struct PayinMethod { pub kind: String, + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub group: Option, - pub required_payment_details: Option, // 🚧 JsonNode + #[serde(skip_serializing_if = "Option::is_none")] + pub required_payment_details: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub fee: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub min: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub max: Option, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)] +#[serde(rename_all = "camelCase")] pub struct PayoutDetails { pub currency_code: String, + #[serde(skip_serializing_if = "Option::is_none")] pub min: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub max: Option, pub methods: Vec, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct PayoutMethod { pub kind: String, + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub group: Option, - pub required_payment_details: Option, // 🚧 JsonNode + #[serde(skip_serializing_if = "Option::is_none")] + pub required_payment_details: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub fee: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub min: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub max: Option, pub estimated_settlement_time: i64, } + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use web5::apid::{ + crypto::key_managers::in_memory_key_manager::InMemoryKeyManager, + dids::methods::did_jwk::DidJwk, dsa::ed25519::Ed25519Generator, + }; + + #[test] + fn can_create_and_sign_and_verify() { + let key_manager = InMemoryKeyManager::new(); + let public_jwk = key_manager + .import_private_jwk(Ed25519Generator::generate()) + .unwrap(); + let did_jwk = DidJwk::from_public_jwk(public_jwk).unwrap(); + + let bearer_did = BearerDid::new(&did_jwk.did.uri, Arc::new(key_manager)).unwrap(); + + let offering = Offering::new( + &bearer_did, + &did_jwk.did.uri, + &OfferingData { + description: "Selling BTC for USD".to_string(), + payout_units_per_payin_unit: "1.5".to_string(), + payin: PayinDetails { + currency_code: "USD".to_string(), + ..Default::default() + }, + payout: PayoutDetails { + currency_code: "BTC".to_string(), + ..Default::default() + }, + required_claims: Some(PresentationDefinition { + id: "7ce4004c-3c38-4853-968b-e411bafcd945".to_string(), + name: None, + purpose: None, + input_descriptors: vec![], + }), + }, + "1.0", + ) + .unwrap(); + + assert_ne!(String::default(), offering.signature); + + let offering_json_string = offering.to_json().unwrap(); + + assert_ne!(String::default(), offering_json_string); + + let parsed_offering = Offering::from_json_string(&offering_json_string).unwrap(); + + assert_eq!(offering, parsed_offering); + } +} diff --git a/crates/tbdex/src/signature.rs b/crates/tbdex/src/signature.rs new file mode 100644 index 00000000..52c67452 --- /dev/null +++ b/crates/tbdex/src/signature.rs @@ -0,0 +1,120 @@ +use crate::jose::{Signer, Verifier}; +use base64::{engine::general_purpose, Engine}; +use josekit::JoseError as JosekitError; +use serde_json::Error as SerdeJsonError; +use serde_json::{Map, Value}; +use sha2::{Digest, Sha256}; +use std::fmt::Debug; +use web5::apid::dids::bearer_did::BearerDid; +use web5::apid::dids::{ + bearer_did::BearerDidError, + resolution::{ + resolution_metadata::ResolutionMetadataError, resolution_result::ResolutionResult, + }, +}; + +#[derive(thiserror::Error, Debug, Clone, PartialEq)] +pub enum SignatureError { + #[error("jose error {0}")] + Jose(String), + #[error(transparent)] + ResolutionMetadata(#[from] ResolutionMetadataError), + #[error(transparent)] + BearerDid(#[from] BearerDidError), + #[error("serde json error {0}")] + SerdeJson(String), +} + +impl From for SignatureError { + fn from(err: SerdeJsonError) -> Self { + SignatureError::SerdeJson(err.to_string()) + } +} + +impl From for SignatureError { + fn from(err: JosekitError) -> Self { + SignatureError::Jose(err.to_string()) + } +} + +type Result = std::result::Result; + +fn canonicalize_json(value: &Value) -> Value { + match value { + Value::Object(map) => { + let mut sorted_map = Map::new(); + let mut keys: Vec<&String> = map.keys().collect(); + keys.sort(); + for key in keys { + sorted_map.insert(key.clone(), canonicalize_json(&map[key])); + } + Value::Object(sorted_map) + } + _ => value.clone(), + } +} + +fn compute_digest(value: &Value) -> Result> { + let canonical_json = canonicalize_json(value); + let canonical_string = serde_json::to_string(&canonical_json)?; + let mut hasher = Sha256::new(); + hasher.update(canonical_string.as_bytes()); + Ok(hasher.finalize().to_vec()) +} + +pub fn sign(bearer_did: &BearerDid, metadata: &Value, data: &Value) -> Result { + let mut combined = Map::new(); + combined.insert("metadata".to_string(), metadata.clone()); + combined.insert("data".to_string(), data.clone()); + + let digest = compute_digest(&Value::Object(combined))?; + + // default to first VM + let key_id = bearer_did.document.verification_method[0].id.clone(); + let web5_signer = bearer_did.get_signer(key_id.clone())?; + let jose_signer = Signer { + kid: key_id, + web5_signer, + }; + let detached_compact_jws = jose_signer.sign_detached_compact_jws(&digest)?; + + Ok(detached_compact_jws) +} + +pub fn verify( + did_uri: &str, + metadata: &Value, + data: &Value, + detached_compact_jws: &str, +) -> Result<()> { + // re-attach the payload + let mut combined = Map::new(); + combined.insert("metadata".to_string(), metadata.clone()); + combined.insert("data".to_string(), data.clone()); + let digest = compute_digest(&Value::Object(combined))?; + let payload = general_purpose::URL_SAFE_NO_PAD.encode(digest); + + let parts: Vec<&str> = detached_compact_jws.split('.').collect(); + if parts.len() != 3 { + return Err(SignatureError::Jose( + "detached compact jws wrong number of parts".to_string(), + )); + } + let message = format!("{}.{}.{}", parts[0], payload, parts[2]); + + let resolution_result = ResolutionResult::new(did_uri); + match resolution_result.resolution_metadata.error { + Some(e) => Err(SignatureError::ResolutionMetadata(e)), + None => { + let document = resolution_result + .document + .ok_or(SignatureError::ResolutionMetadata( + ResolutionMetadataError::InternalError, + ))?; + + Verifier::verify_compact_jws(document, message)?; + + Ok(()) + } + } +} diff --git a/tbdex-rs.code-workspace b/tbdex-rs.code-workspace deleted file mode 100644 index 66e7fcb2..00000000 --- a/tbdex-rs.code-workspace +++ /dev/null @@ -1,27 +0,0 @@ -{ - "folders": [ - { - "name": "root", - "path": "." - }, - { - "path": "crates/protocol" - } - ], - "settings": { - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/Thumbs.db": true, - "examples/**/_ftl": true, - "**/node_modules": true, - "**/go.work": true, - "**/go.work.sum": true, - ".hermit": true, - "**/*.zip": true, - } - } -}