Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions xmtp-ffi/include/xmtp_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,33 @@ enum XmtpFfiDeliveryStatus
typedef int32_t XmtpFfiDeliveryStatus;
#endif // __cplusplus

enum XmtpFfiReactionAction
#ifdef __cplusplus
: int32_t
#endif // __cplusplus
{
XMTP_FFI_REACTION_ACTION_UNSPECIFIED = 0,
XMTP_FFI_REACTION_ACTION_ADDED = 1,
XMTP_FFI_REACTION_ACTION_REMOVED = 2,
};
#ifndef __cplusplus
typedef int32_t XmtpFfiReactionAction;
#endif // __cplusplus

enum XmtpFfiReactionSchema
#ifdef __cplusplus
: int32_t
#endif // __cplusplus
{
XMTP_FFI_REACTION_SCHEMA_UNSPECIFIED = 0,
XMTP_FFI_REACTION_SCHEMA_UNICODE = 1,
XMTP_FFI_REACTION_SCHEMA_SHORTCODE = 2,
XMTP_FFI_REACTION_SCHEMA_CUSTOM = 3,
};
#ifndef __cplusplus
typedef int32_t XmtpFfiReactionSchema;
#endif // __cplusplus

/**
* Consent entity type.
*/
Expand Down Expand Up @@ -515,6 +542,18 @@ typedef struct XmtpFfiGroupPermissions {
struct XmtpFfiPermissionPolicySet policy_set;
} XmtpFfiGroupPermissions;

/**
* Ffi data for Reaction content
*/
typedef struct XmtpFfiReaction {
char *reference;
char *reference_inbox_id;
char *sender_inbox_id;
XmtpFfiReactionAction action;
char *content;
XmtpFfiReactionSchema schema;
} XmtpFfiReaction;

/**
* An enriched (decoded) message exposed to C.
* Contains metadata + the original encoded content bytes for upper-layer decoding.
Expand Down Expand Up @@ -558,6 +597,10 @@ typedef struct XmtpFfiEnrichedMessage {
* Expiration timestamp in nanoseconds (0 = no expiration).
*/
int64_t expires_at_ns;
/**
* Array of reactions
*/
struct XmtpFfiReaction *reactions;
/**
* Number of reactions.
*/
Expand Down
33 changes: 33 additions & 0 deletions xmtp-ffi/src/conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,38 @@ pub(crate) fn decoded_to_enriched(
let len = b.len() as i32;
(Box::into_raw(b) as *mut u8, len)
};

let reactions_vec: Vec<FfiReaction> = msg
.reactions
.iter()
.filter_map(|r| match &r.content {
xmtp_mls::messages::decoded_message::MessageBody::Reaction(reaction) => Some(FfiReaction {
reference: to_c_string(&hex::encode(&reaction.reference)),
reference_inbox_id: to_c_string(&reaction.reference_inbox_id),
sender_inbox_id: to_c_string(&r.metadata.sender_inbox_id),
action: match reaction.action() {
xmtp_proto::xmtp::mls::message_contents::content_types::ReactionAction::Added => FfiReactionAction::Added,
xmtp_proto::xmtp::mls::message_contents::content_types::ReactionAction::Removed => FfiReactionAction::Removed,
xmtp_proto::xmtp::mls::message_contents::content_types::ReactionAction::Unspecified => FfiReactionAction::Unspecified,
},
content: to_c_string(&reaction.content),
schema: match reaction.schema() {
xmtp_proto::xmtp::mls::message_contents::content_types::ReactionSchema::Unspecified => FfiReactionSchema::Unspecified,
xmtp_proto::xmtp::mls::message_contents::content_types::ReactionSchema::Unicode => FfiReactionSchema::Unicode,
xmtp_proto::xmtp::mls::message_contents::content_types::ReactionSchema::Shortcode => FfiReactionSchema::Shortcode,
xmtp_proto::xmtp::mls::message_contents::content_types::ReactionSchema::Custom => FfiReactionSchema::Custom,
},
}),
_ => None,
})
.collect();

let reactions = if reactions_vec.is_empty() {
std::ptr::null_mut()
} else {
Box::into_raw(reactions_vec.into_boxed_slice()) as *mut FfiReaction
Comment on lines +1561 to +1564
};

FfiEnrichedMessage {
id: to_c_string(&hex::encode(&msg.metadata.id)),
group_id: to_c_string(&hex::encode(&msg.metadata.group_id)),
Expand All @@ -1556,6 +1588,7 @@ pub(crate) fn decoded_to_enriched(
None => std::ptr::null_mut(),
},
expires_at_ns: msg.metadata.expires_at_ns.unwrap_or(0),
reactions,
num_reactions: msg.reactions.len() as i32,
num_replies: msg.num_replies as i32,
content_bytes,
Expand Down
31 changes: 31 additions & 0 deletions xmtp-ffi/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::cell::RefCell;
use std::ffi::{CStr, CString, c_char};
use std::sync::OnceLock;

use tokio::runtime::Runtime;

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -205,6 +206,23 @@ pub enum FfiPreferenceUpdateKind {
HmacKey = 1,
}

#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FfiReactionAction {
Unspecified = 0,
Added = 1,
Removed = 2,
}

#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FfiReactionSchema {
Unspecified = 0,
Unicode = 1,
Shortcode = 2,
Custom = 3,
}

// ---------------------------------------------------------------------------
// Data transfer types (flat, repr(C))
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -438,6 +456,17 @@ pub struct FfiGroupMetadata {
pub conversation_type: FfiConversationType,
}

/// Ffi data for Reaction content
#[repr(C)]
pub struct FfiReaction {
pub reference: *mut c_char,
pub reference_inbox_id: *mut c_char,
pub sender_inbox_id: *mut c_char,
pub action: FfiReactionAction,
pub content: *mut c_char,
pub schema: FfiReactionSchema,
}

/// Permission policy set for a conversation.
#[repr(C)]
pub struct FfiPermissionPolicySet {
Expand Down Expand Up @@ -483,6 +512,8 @@ pub struct FfiEnrichedMessage {
pub fallback_text: *mut c_char,
/// Expiration timestamp in nanoseconds (0 = no expiration).
pub expires_at_ns: i64,
/// Array of reactions
pub reactions: *mut FfiReaction,
/// Number of reactions.
pub num_reactions: i32,
/// Number of replies.
Expand Down
68 changes: 63 additions & 5 deletions xmtp-sys/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ pub enum XmtpFfiDeliveryStatus {
}
#[repr(i32)]
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum XmtpFfiReactionAction {
XMTP_FFI_REACTION_ACTION_UNSPECIFIED = 0,
XMTP_FFI_REACTION_ACTION_ADDED = 1,
XMTP_FFI_REACTION_ACTION_REMOVED = 2,
}
#[repr(i32)]
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum XmtpFfiReactionSchema {
XMTP_FFI_REACTION_SCHEMA_UNSPECIFIED = 0,
XMTP_FFI_REACTION_SCHEMA_UNICODE = 1,
XMTP_FFI_REACTION_SCHEMA_SHORTCODE = 2,
XMTP_FFI_REACTION_SCHEMA_CUSTOM = 3,
}
#[repr(i32)]
#[non_exhaustive]
#[doc = " Consent entity type."]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum XmtpFfiConsentEntityType {
Expand Down Expand Up @@ -693,6 +710,43 @@ impl Default for XmtpFfiGroupPermissions {
}
}
}
#[doc = " Ffi data for Reaction content"]
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct XmtpFfiReaction {
pub reference: *mut ::core::ffi::c_char,
pub reference_inbox_id: *mut ::core::ffi::c_char,
pub sender_inbox_id: *mut ::core::ffi::c_char,
pub action: XmtpFfiReactionAction,
pub content: *mut ::core::ffi::c_char,
pub schema: XmtpFfiReactionSchema,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of XmtpFfiReaction"][::core::mem::size_of::<XmtpFfiReaction>() - 48usize];
["Alignment of XmtpFfiReaction"][::core::mem::align_of::<XmtpFfiReaction>() - 8usize];
["Offset of field: XmtpFfiReaction::reference"]
[::core::mem::offset_of!(XmtpFfiReaction, reference) - 0usize];
["Offset of field: XmtpFfiReaction::reference_inbox_id"]
[::core::mem::offset_of!(XmtpFfiReaction, reference_inbox_id) - 8usize];
["Offset of field: XmtpFfiReaction::sender_inbox_id"]
[::core::mem::offset_of!(XmtpFfiReaction, sender_inbox_id) - 16usize];
["Offset of field: XmtpFfiReaction::action"]
[::core::mem::offset_of!(XmtpFfiReaction, action) - 24usize];
["Offset of field: XmtpFfiReaction::content"]
[::core::mem::offset_of!(XmtpFfiReaction, content) - 32usize];
["Offset of field: XmtpFfiReaction::schema"]
[::core::mem::offset_of!(XmtpFfiReaction, schema) - 40usize];
};
impl Default for XmtpFfiReaction {
fn default() -> Self {
let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
unsafe {
::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
#[doc = " An enriched (decoded) message exposed to C.\n Contains metadata + the original encoded content bytes for upper-layer decoding."]
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
Expand All @@ -717,6 +771,8 @@ pub struct XmtpFfiEnrichedMessage {
pub fallback_text: *mut ::core::ffi::c_char,
#[doc = " Expiration timestamp in nanoseconds (0 = no expiration)."]
pub expires_at_ns: i64,
#[doc = " Array of reactions"]
pub reactions: *mut XmtpFfiReaction,
#[doc = " Number of reactions."]
pub num_reactions: i32,
#[doc = " Number of replies."]
Expand All @@ -728,7 +784,7 @@ pub struct XmtpFfiEnrichedMessage {
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of XmtpFfiEnrichedMessage"][::core::mem::size_of::<XmtpFfiEnrichedMessage>() - 104usize];
["Size of XmtpFfiEnrichedMessage"][::core::mem::size_of::<XmtpFfiEnrichedMessage>() - 112usize];
["Alignment of XmtpFfiEnrichedMessage"]
[::core::mem::align_of::<XmtpFfiEnrichedMessage>() - 8usize];
["Offset of field: XmtpFfiEnrichedMessage::id"]
Expand All @@ -753,14 +809,16 @@ const _: () = {
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, fallback_text) - 64usize];
["Offset of field: XmtpFfiEnrichedMessage::expires_at_ns"]
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, expires_at_ns) - 72usize];
["Offset of field: XmtpFfiEnrichedMessage::reactions"]
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, reactions) - 80usize];
["Offset of field: XmtpFfiEnrichedMessage::num_reactions"]
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, num_reactions) - 80usize];
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, num_reactions) - 88usize];
["Offset of field: XmtpFfiEnrichedMessage::num_replies"]
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, num_replies) - 84usize];
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, num_replies) - 92usize];
["Offset of field: XmtpFfiEnrichedMessage::content_bytes"]
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, content_bytes) - 88usize];
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, content_bytes) - 96usize];
["Offset of field: XmtpFfiEnrichedMessage::content_bytes_len"]
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, content_bytes_len) - 96usize];
[::core::mem::offset_of!(XmtpFfiEnrichedMessage, content_bytes_len) - 104usize];
};
impl Default for XmtpFfiEnrichedMessage {
fn default() -> Self {
Expand Down
50 changes: 50 additions & 0 deletions xmtp/src/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use prost::Message as ProstMessage;

use crate::conversation::{Conversation, Message};
use crate::error::Result;
use crate::ffi::take_c_string;
use crate::types::SendOptions;

/// Content type identifier on the XMTP network.
Expand Down Expand Up @@ -91,6 +92,18 @@ pub enum ReactionAction {
Removed = 2,
}

impl ReactionAction {
/// Convert FFI `ReactionAction` (i32 enum) into the safe SDK type.
pub const fn from_ffi(v: i32) -> Option<Self> {
match v {
0 => Some(Self::Unspecified),
1 => Some(Self::Added),
2 => Some(Self::Removed),
_ => None,
}
}
}

/// Reaction content schema.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, prost::Enumeration)]
#[repr(i32)]
Expand All @@ -105,6 +118,19 @@ pub enum ReactionSchema {
Custom = 3,
}

impl ReactionSchema {
/// Convert FFI `ReactionSchema` (i32 enum) into the safe SDK type.
pub const fn from_ffi(v: i32) -> Option<Self> {
match v {
0 => Some(Self::Unspecified),
1 => Some(Self::Unicode),
2 => Some(Self::Shortcode),
3 => Some(Self::Custom),
_ => None,
}
}
}

/// Metadata for a remotely hosted encrypted attachment.
#[derive(Clone, PartialEq, Eq, Hash, ProstMessage)]
pub struct RemoteAttachmentInfo {
Expand Down Expand Up @@ -292,6 +318,8 @@ pub struct Reaction {
pub reference: String,
/// Inbox ID of the referenced message's sender.
pub reference_inbox_id: String,
/// Inbox ID of the reaction sender.
pub sender_inbox_id: String,
/// Reaction action.
pub action: ReactionAction,
/// The emoji / shortcode / custom content.
Expand All @@ -300,6 +328,27 @@ pub struct Reaction {
pub schema: ReactionSchema,
}

impl Reaction {
/// Convert a single FFI reaction (already dereferenced) into the safe SDK type.
pub(crate) fn from_ffi(ffi: &xmtp_sys::XmtpFfiReaction) -> Self {
Self {
// SAFETY: `reference` is a C string allocated by the FFI layer (or null, handled by `take_c_string`).
reference: unsafe { take_c_string(ffi.reference) }.unwrap_or_default(),
// SAFETY: `reference_inbox_id` is a C string allocated by the FFI layer (or null, handled by `take_c_string`).
reference_inbox_id: unsafe { take_c_string(ffi.reference_inbox_id) }
.unwrap_or_default(),
// SAFETY: `sender_inbox_id` is a C string allocated by the FFI layer (or null, handled by `take_c_string`).
sender_inbox_id: unsafe { take_c_string(ffi.sender_inbox_id) }.unwrap_or_default(),
action: ReactionAction::from_ffi(ffi.action as i32)
.unwrap_or(ReactionAction::Unspecified),
// SAFETY: `content` is a C string allocated by the FFI layer (or null, handled by `take_c_string`).
content: unsafe { take_c_string(ffi.content) }.unwrap_or_default(),
schema: ReactionSchema::from_ffi(ffi.schema as i32)
.unwrap_or(ReactionSchema::Unspecified),
}
}
}

/// A decoded reply.
#[derive(Debug, Clone)]
pub struct Reply {
Expand Down Expand Up @@ -506,6 +555,7 @@ pub fn decode(raw: &[u8]) -> Result<Content> {
Ok(Content::Reaction(Reaction {
reference: rv2.reference,
reference_inbox_id: rv2.reference_inbox_id,
sender_inbox_id: String::new(),
action: ReactionAction::try_from(rv2.action).unwrap_or(ReactionAction::Unspecified),
content: rv2.content,
schema: ReactionSchema::try_from(rv2.schema).unwrap_or(ReactionSchema::Unspecified),
Expand Down
Loading
Loading