fix(saved-devices): preserve targeted transfer direction

This commit is contained in:
2026-08-14 09:17:50 +02:00
parent 0ea9a8e49c
commit ce8f19b9ff
15 changed files with 103 additions and 24 deletions

View File

@@ -88,10 +88,18 @@ pub enum TargetedTransferState {
Deleted,
}
/// This installation's immutable role in a Targeted transfer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
pub enum TargetedTransferRole {
Sender,
Receiver,
}
/// Immutable recipient-bound transfer snapshot, separate from an ordinary share.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
pub struct TargetedTransfer {
pub id: String,
pub role: TargetedTransferRole,
pub sender_endpoint_id: String,
pub receiver_endpoint_id: String,
pub manifest_id: String,

View File

@@ -32,8 +32,8 @@ pub use api::{
PairingEligibilitySummary, PendingTargetedOffer, PublishedOutput, ReceiveOutputSink,
ReceiveOutputSinkV2, ReceivedArtifact, ReceivedLocatorKind, ReceiverRequest, RuntimeStatus,
SavedDevice, SavedDeviceCapabilities, ShareMetadataInput, ShareResult, ShareSource, SourceKind,
StoredTransfer, TargetedOfferResponse, TargetedTransfer, TargetedTransferState,
TicketInspection, TransferAccessMode, TransferMetadata,
StoredTransfer, TargetedOfferResponse, TargetedTransfer, TargetedTransferRole,
TargetedTransferState, TicketInspection, TransferAccessMode, TransferMetadata,
};
pub use error::VnidropError;
pub use runtime::VnidropCore;

View File

@@ -11,6 +11,7 @@ mod state;
mod store;
mod store_outbox;
pub(crate) use crate::api::TargetedTransferRole;
pub(crate) use auth::{
auth_secret_material, reconstruct_authorization, TargetedAuthorization,
TargetedAuthorizationDraft,
@@ -18,6 +19,4 @@ pub(crate) use auth::{
pub(crate) use inbox::{RespondError, TargetedOfferInbox};
pub(crate) use protocol::TargetedTransferProtocol;
pub(crate) use schema::ensure_schema;
pub(crate) use store::{
state_as_str, TargetedTransferRole, TargetedTransferRow, TargetedTransferStore,
};
pub(crate) use store::{state_as_str, TargetedTransferRow, TargetedTransferStore};

View File

@@ -6,7 +6,7 @@
use sqlx::{Row, SqlitePool};
use crate::{
api::{TargetedTransfer, TargetedTransferState},
api::{TargetedTransfer, TargetedTransferRole, TargetedTransferState},
error::VnidropError,
util::now_ms,
};
@@ -430,7 +430,7 @@ impl TargetedTransferStore {
pub(crate) async fn get(&self, id: &str) -> Result<Option<TargetedTransfer>, VnidropError> {
let row = sqlx::query(
r#"
SELECT id, sender_endpoint_id, receiver_endpoint_id, manifest_id, transfer_name,
SELECT id, role, sender_endpoint_id, receiver_endpoint_id, manifest_id, transfer_name,
file_count, total_size, verified_bytes, state, created_at, updated_at
FROM targeted_transfers WHERE id = ?1
"#,
@@ -465,7 +465,7 @@ impl TargetedTransferStore {
pub(crate) async fn list(&self) -> Result<Vec<TargetedTransfer>, VnidropError> {
let rows = sqlx::query(
r#"
SELECT id, sender_endpoint_id, receiver_endpoint_id, manifest_id, transfer_name,
SELECT id, role, sender_endpoint_id, receiver_endpoint_id, manifest_id, transfer_name,
file_count, total_size, verified_bytes, state, created_at, updated_at
FROM targeted_transfers
ORDER BY updated_at DESC
@@ -692,12 +692,6 @@ impl TargetedTransferStore {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TargetedTransferRole {
Sender,
Receiver,
}
#[derive(Debug, Clone)]
pub(crate) struct TargetedTransferRow {
pub(crate) id: String,
@@ -721,6 +715,7 @@ pub(crate) struct TargetedTransferRow {
fn row_to_transfer(row: sqlx::sqlite::SqliteRow) -> Result<TargetedTransfer, VnidropError> {
Ok(TargetedTransfer {
id: row.get("id"),
role: parse_role(&row.get::<String, _>("role"))?,
sender_endpoint_id: row.get("sender_endpoint_id"),
receiver_endpoint_id: row.get("receiver_endpoint_id"),
manifest_id: row.get("manifest_id"),

View File

@@ -856,6 +856,11 @@ fn apple_public_bindings_omit_raw_secrets_and_generic_mutation() {
&& source.contains("public func savedDeviceCapabilities()"),
"Swift bindings must expose production saved-device capabilities"
);
assert!(
source.contains("public var role: TargetedTransferRole")
&& source.contains("public enum TargetedTransferRole"),
"Swift targeted-transfer snapshots must expose their persisted role"
);
assert!(
source.contains("setSavedDeviceLabel"),
"Swift bindings must expose saved-device rename"

View File

@@ -12,8 +12,8 @@ use crate::{
secure_secret::{FaultInjectingSecretStore, ReferenceStoreFailure},
CoreEvent, CoreEventSink, CoreNetworkConfig, CoreRelayMode, DeviceRelationshipState,
PendingTargetedOffer, PublishedOutput, ReceiveOutputSink, ReceiveOutputSinkV2,
ReceivedLocatorKind, ShareMetadataInput, ShareSource, SourceKind, TargetedTransferState,
TransferAccessMode, VnidropCore, VnidropError,
ReceivedLocatorKind, ShareMetadataInput, ShareSource, SourceKind, TargetedTransferRole,
TargetedTransferState, TransferAccessMode, VnidropCore, VnidropError,
};
struct RecordingSink {
@@ -381,6 +381,7 @@ fn identity_reset_cancels_targeted_authorization_bound_to_the_lost_endpoint() {
.get_targeted_transfer(transfer.id.clone())
.unwrap()
.unwrap();
assert_eq!(snapshot.role, TargetedTransferRole::Sender);
assert_eq!(snapshot.state, TargetedTransferState::Cancelled);
assert!(recovered
.targeted_blob_ticket_for_test(transfer.id)

View File

@@ -4,7 +4,7 @@ use support::TestNode;
use vnidrop::{
saved_device_capabilities, DeviceRelationship, DeviceRelationshipState, SavedDevice,
SavedDeviceCapabilities, ShareMetadataInput, ShareSource, SourceKind, TargetedTransfer,
TargetedTransferState, TransferAccessMode, VnidropError,
TargetedTransferRole, TargetedTransferState, TransferAccessMode, VnidropError,
};
#[test]
@@ -38,6 +38,7 @@ fn saved_devices_relationships_and_targeted_transfers_are_distinct_contracts() {
};
let transfer = TargetedTransfer {
id: "targeted-transfer-id".to_string(),
role: TargetedTransferRole::Sender,
sender_endpoint_id: "sender-endpoint".to_string(),
receiver_endpoint_id: device.endpoint_id.clone(),
manifest_id: "immutable-manifest-id".to_string(),