1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Signals which can be emitted from within Holochain, out across an interface.
//! There are two main kinds of Signal: system-defined, and app-defined:
//! - App-defined signals are produced via the `emit_signal` host function.
//! - System-defined signals are produced in various places in the system
use crate::app::{InstalledAppId, UnrecoverableCellReason};
use crate::impl_from;
use holochain_serialized_bytes::prelude::*;
use holochain_zome_types::prelude::*;
/// A Signal is some information emitted from within Holochain out through
/// an Interface
#[derive(Clone, Debug, Serialize, Deserialize, SerializedBytes, PartialEq, Eq)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
#[cfg_attr(feature = "ts_rs", derive(ts_rs::TS))]
#[cfg_attr(feature = "ts_rs", ts(export_to = "api/app/types.ts"))]
pub enum Signal {
/// Signal from a Cell, generated by `emit_signal`
App {
/// The Cell from which the signal was emitted
cell_id: CellId,
/// The Zome from which the signal was emitted
zome_name: ZomeName,
/// The actual signal that was emitted
signal: AppSignal,
},
/// A direct signal, sent with an app call to `SendDirectSignal`.
AppDirect {
/// The receiving `CellId`.
cell_id: CellId,
/// The agent who sent the signal.
from_agent: AgentPubKey,
/// The payload sent by the remote agent.
signal: Vec<u8>,
},
/// System-defined signals
System(SystemSignal),
}
impl Signal {
/// Parse from vec.
pub fn try_from_vec(v: Vec<u8>) -> Result<Self, SerializedBytesError> {
Self::try_from(SerializedBytes::from(UnsafeBytes::from(v)))
}
}
/// A Signal which originates from within the Holochain system, as opposed to
/// from within a Cell
#[derive(Clone, Debug, Serialize, Deserialize, SerializedBytes, PartialEq, Eq)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
#[cfg_attr(feature = "ts_rs", derive(ts_rs::TS))]
#[cfg_attr(feature = "ts_rs", ts(export_to = "api/app/types.ts"))]
pub enum SystemSignal {
/// A countersigning session has successfully completed.
SuccessfulCountersigning(EntryHash),
/// A countersigning session has been abandoned.
AbandonedCountersigning(EntryHash),
/// A single cell within an app has finished restoring its source chain from the DHT.
RestoreComplete {
/// The cell that finished restoring.
cell_id: CellId,
},
/// All cells of an app have finished restoring; the app is now Disabled(NeverStarted).
AppRestoreComplete {
/// The app that finished restoring.
installed_app_id: InstalledAppId,
},
/// A cell's restore hit a permanent failure (e.g. validated ChainIntegrityWarrant). The whole
/// app transitions to [`crate::app::AppStatus::Unrecoverable`] at the same moment.
RestoreFailed {
/// The cell that failed to restore.
cell_id: CellId,
/// Why the restore is unrecoverable.
reason: UnrecoverableCellReason,
},
}
impl_from! {
SystemSignal => Signal, |s| { Self::System(s) },
}
/// The maximum size that Holochain will permit sending or receiving in a single direct signal.
pub const DIRECT_SIGNAL_MAX_SIZE: usize = 1024 * 1024;
/// Wrapper type for transmitting a signed direct signal over the network.
///
/// The payload is encoded as msgpack `bin` (one byte per byte) and the sender
/// signs the SHA2-512 hash of the encoding, never the raw bytes: lair signing
/// frames are capped at 8 KiB. The `bin` encoding starts with a msgpack bin
/// header, so the signed hash can never collide with a signed zome call,
/// whose parameters encode as a msgpack map.
#[derive(Debug, Clone, Serialize, Deserialize, SerializedBytes)]
pub struct DirectSignal(#[serde(with = "serde_bytes")] pub Vec<u8>);
#[cfg(test)]
mod tests {
use super::*;
use crate::app::{UnrecoverableCellReason, WarrantSummary};
use ::fixt::prelude::*;
use holo_hash::fixt::*;
#[test]
fn restore_system_signals_serde_round_trip() {
let cell_id = CellId::new(fixt!(DnaHash), fixt!(AgentPubKey));
let summary = WarrantSummary {
author: fixt!(AgentPubKey),
warrantee: fixt!(AgentPubKey),
timestamp: Timestamp::from_micros(1_000_000),
};
let reason = UnrecoverableCellReason::ChainForkWarrant(Box::new(summary));
let signals: Vec<SystemSignal> = vec![
SystemSignal::RestoreComplete {
cell_id: cell_id.clone(),
},
SystemSignal::AppRestoreComplete {
installed_app_id: "my_app".to_string(),
},
SystemSignal::RestoreFailed {
cell_id: cell_id.clone(),
reason: reason.clone(),
},
];
for signal in &signals {
let json = serde_json::to_string(signal).unwrap();
let recovered: SystemSignal = serde_json::from_str(&json).unwrap();
assert_eq!(signal, &recovered);
}
}
/// A max-size payload must encode as msgpack `bin` (1 byte per payload
/// byte plus a 5-byte bin32 header), never as an array of ints, or the
/// receiver's pre-decode size bound would reject legitimate signals.
#[test]
fn direct_signal_max_payload_encodes_as_bin() {
// 0xFF forces the worst case: an array-of-ints encoding would need
// two bytes per element and blow past the bound.
let signal = DirectSignal(vec![0xFF; DIRECT_SIGNAL_MAX_SIZE]);
let bytes = holochain_serialized_bytes::encode(&signal).unwrap();
assert!(bytes.len() >= DIRECT_SIGNAL_MAX_SIZE);
assert!(
bytes.len() <= DIRECT_SIGNAL_MAX_SIZE + 5,
"encoded direct signal is {} bytes, bound is {}",
bytes.len(),
DIRECT_SIGNAL_MAX_SIZE + 5
);
let decoded: DirectSignal = holochain_serialized_bytes::decode(&bytes).unwrap();
assert_eq!(decoded.0, signal.0);
}
}