darkirc.rs 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. collections::{HashMap, HashSet},
  20. io::Cursor,
  21. sync::{atomic::Ordering, Arc, OnceLock, Weak},
  22. time::UNIX_EPOCH,
  23. };
  24. use async_lock::RwLock;
  25. use crypto_box::{ChaChaBox, PublicKey, SecretKey};
  26. use darkfi::{
  27. event_graph::{
  28. self,
  29. proto::{EventPut, ProtocolEventGraph},
  30. EventGraph, EventGraphConfig, EventGraphPtr,
  31. },
  32. net::{
  33. dnet::DnetEvent,
  34. session::SESSION_DEFAULT,
  35. settings::{MagicBytes, NetworkProfile, Settings as NetSettings},
  36. ChannelPtr, P2p, P2pPtr,
  37. },
  38. system::{sleep, Subscription},
  39. Result as DarkFiResult,
  40. };
  41. use darkfi_serial::{
  42. deserialize_async, serialize, serialize_async, AsyncEncodable, Decodable, Encodable,
  43. };
  44. use irc2::{
  45. crypto::saltbox,
  46. irc::{server::MAX_NICK_LEN, IrcChannel, IrcContact},
  47. pad, unpad, Privmsg,
  48. };
  49. use kvdb_overlay::Database as KvDb;
  50. use parking_lot::Mutex as SyncMutex;
  51. use crate::{
  52. app::schema::{
  53. ensure_joined_channels_seeded,
  54. menu::{channel::Channel, contact::Contact},
  55. read_joined_channels,
  56. },
  57. db::AppDbPtr,
  58. error::{Error, Result},
  59. prop::{
  60. BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyEnum, PropertyPtr, PropertyStr,
  61. Role,
  62. },
  63. scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak, Slot},
  64. ui::{
  65. chatview::{MessageId, Timestamp},
  66. OnModify,
  67. },
  68. ExecutorPtr,
  69. };
  70. const P2P_RETRY_TIME: u64 = 20;
  71. const COOLOFF_SLEEP_TIME: u64 = 20;
  72. const COOLOFF_SYNC_ATTEMPTS: usize = 6;
  73. const SYNC_MIN_PEERS: usize = 2;
  74. /// Event graph rotation: `max_dags` hourly slots (`hours_rotation: 1`).
  75. const DAGS_COUNT: u64 = 24;
  76. /// Milliseconds in one rotation period (1 hour).
  77. const HOUR_MS: u64 = 3_600_000;
  78. pub(crate) const P2P_OUTBOUND_ACTIVE: usize = 3;
  79. const P2P_OUTBOUND_SLEEP: usize = 1;
  80. /// Update `outbound_peers` property useful for diagnostics
  81. const DNET_ENABLED: bool = true;
  82. /// Due to drift between different machine's clocks, if the message timestamp is recent
  83. /// then we will just correct it to the current time so messages appear sequential in the UI.
  84. const RECENT_TIME_DIST: u64 = 25_000;
  85. // NOTE: if `paths` already lives in a shared module (e.g. `super::paths` from
  86. // darkirc.rs's parent module), delete this block and add `use super::paths::*;`
  87. // instead. Duplicated here so this file compiles standalone.
  88. #[cfg(target_os = "android")]
  89. mod paths {
  90. use crate::android::{get_appdata_path, get_external_storage_path};
  91. use std::path::PathBuf;
  92. pub fn get_chatdb_path() -> PathBuf {
  93. get_external_storage_path().join("chatdb")
  94. }
  95. pub fn p2p_datastore_path() -> PathBuf {
  96. get_appdata_path().join("darkirc2_p2p")
  97. }
  98. pub fn hostlist_path() -> PathBuf {
  99. get_appdata_path().join("hostlist2.tsv")
  100. }
  101. }
  102. #[cfg(not(target_os = "android"))]
  103. mod paths {
  104. use std::path::PathBuf;
  105. pub fn get_chatdb_path() -> PathBuf {
  106. dirs::data_local_dir().unwrap().join("darkfi/app/chatdb")
  107. }
  108. pub fn p2p_datastore_path() -> PathBuf {
  109. dirs::cache_dir().unwrap().join("darkfi/app/darkirc2_p2p")
  110. }
  111. pub fn hostlist_path() -> PathBuf {
  112. dirs::cache_dir().unwrap().join("darkfi/app/hostlist2.tsv")
  113. }
  114. }
  115. use paths::*;
  116. macro_rules! t { ($($arg:tt)*) => { trace!(target: "plugin::darkirc2", $($arg)*); } }
  117. macro_rules! d { ($($arg:tt)*) => { debug!(target: "plugin::darkirc2", $($arg)*); } }
  118. macro_rules! i { ($($arg:tt)*) => { info!(target: "plugin::darkirc2", $($arg)*); } }
  119. macro_rules! e { ($($arg:tt)*) => { error!(target: "plugin::darkirc2", $($arg)*); } }
  120. macro_rules! w { ($($arg:tt)*) => { warn!(target: "plugin::darkirc2", $($arg)*); } }
  121. struct SeenMsg {
  122. id: MessageId,
  123. is_self: bool,
  124. seen_times: usize,
  125. }
  126. struct SeenMessages {
  127. seen: Vec<SeenMsg>,
  128. }
  129. impl SeenMessages {
  130. fn new() -> Self {
  131. Self { seen: vec![] }
  132. }
  133. fn get_status(&self, id: &MessageId) -> Option<&SeenMsg> {
  134. self.seen.iter().find(|s| s.id == *id)
  135. }
  136. fn push(&mut self, id: MessageId, is_self: bool) {
  137. self.seen.push(SeenMsg { id, is_self, seen_times: 0 });
  138. }
  139. }
  140. pub type DarkIrcPtr = Arc<DarkIrc>;
  141. pub struct DarkIrc {
  142. node: SceneNodeWeak,
  143. tasks: SyncMutex<Vec<smol::Task<()>>>,
  144. p2p: P2pPtr,
  145. event_graph: EventGraphPtr,
  146. seen_msgs: SyncMutex<SeenMessages>,
  147. nick: PropertyStr,
  148. chat_is_enabled: PropertyBool,
  149. net_transport: PropertyEnum,
  150. pub channels: RwLock<HashMap<String, IrcChannel>>,
  151. pub contacts: RwLock<HashMap<String, IrcContact>>,
  152. app_db: AppDbPtr,
  153. dm_secret: SecretKey,
  154. ex: ExecutorPtr,
  155. }
  156. impl DarkIrc {
  157. pub async fn new(
  158. node: SceneNodeWeak,
  159. sg_root: SceneNodePtr,
  160. ex: ExecutorPtr,
  161. kv_db: KvDb,
  162. app_db: AppDbPtr,
  163. ) -> Result<Pimpl> {
  164. let node_ref = &node.upgrade().unwrap();
  165. let nick = PropertyStr::wrap(node_ref, Role::Internal, "nick", 0).unwrap();
  166. let setting_node = sg_root.lookup_node("/setting").unwrap();
  167. let chat_is_enabled =
  168. PropertyBool::wrap(&setting_node, Role::User, "chat.is_enabled", 0).unwrap();
  169. let net_transport =
  170. PropertyEnum::wrap(&setting_node, Role::Internal, "net.transport", 0).unwrap();
  171. i!("Starting DarkIRC backend");
  172. let dm_secret_bytes = app_db.dm_secret().await?;
  173. let dm_secret = SecretKey::from_bytes(dm_secret_bytes);
  174. let dm_public_b58 = bs58::encode(dm_secret.public_key().to_bytes()).into_string();
  175. // Expose our DM public key on the plugin node so it can be displayed/shared.
  176. node_ref
  177. .set_property_str(
  178. &mut PropertyAtomicGuard::none(),
  179. Role::Internal,
  180. "dm_public",
  181. &dm_public_b58,
  182. )
  183. .unwrap();
  184. i!("DM identity public key (share with contacts): {dm_public_b58}");
  185. let mut p2p_settings: NetSettings = Default::default();
  186. p2p_settings.magic_bytes = MagicBytes([251, 229, 199, 181]);
  187. p2p_settings.app_version = semver::Version::parse("0.5.0").unwrap();
  188. p2p_settings.app_name = "darkirc".to_string();
  189. p2p_settings.inbound_connections = 2;
  190. let transport = net_transport.get();
  191. Self::apply_transport_settings(&mut p2p_settings, &transport);
  192. p2p_settings.p2p_datastore = p2p_datastore_path().into_os_string().into_string().ok();
  193. p2p_settings.hostlist = hostlist_path().into_os_string().into_string().ok();
  194. let p2p = match P2p::new(p2p_settings.clone(), ex.clone()).await {
  195. Ok(p2p) => p2p,
  196. Err(err) => {
  197. e!("Create p2p network failed: {err}!");
  198. return Err(Error::ServiceFailed)
  199. }
  200. };
  201. if DNET_ENABLED {
  202. i!("Enabling dnet outbound-slot event stream for outbound_peers property");
  203. p2p.dnet_enable();
  204. }
  205. let event_graph = match EventGraph::new(
  206. p2p.clone(),
  207. kv_db.clone(),
  208. std::path::PathBuf::new(),
  209. false,
  210. EventGraphConfig {
  211. initial_genesis: 1_704_067_200_000,
  212. hours_rotation: 1,
  213. genesis_contents: b"darkirc-v1".to_vec(),
  214. rln_enabled: false,
  215. pregenerated_identity_commitments: vec![],
  216. max_dags: Some(24),
  217. },
  218. ex.clone(),
  219. )
  220. .await
  221. {
  222. Ok(evgr) => evgr,
  223. Err(err) => {
  224. e!("Create event graph failed: {err}!");
  225. return Err(Error::ServiceFailed)
  226. }
  227. };
  228. if let Some(prev_nick) = app_db.nick_get().await? {
  229. nick.set(&mut PropertyAtomicGuard::none(), prev_nick);
  230. }
  231. let self_ = Arc::new(Self {
  232. node: node.clone(),
  233. tasks: SyncMutex::new(vec![]),
  234. p2p,
  235. event_graph,
  236. seen_msgs: SyncMutex::new(SeenMessages::new()),
  237. nick,
  238. chat_is_enabled,
  239. net_transport,
  240. channels: RwLock::new(HashMap::new()),
  241. contacts: RwLock::new(HashMap::new()),
  242. app_db,
  243. dm_secret,
  244. ex: ex.clone(),
  245. });
  246. self_.p2p.settings().write().await.outbound_connections = P2P_OUTBOUND_ACTIVE;
  247. ensure_joined_channels_seeded();
  248. self_.load_channels_from_db().await;
  249. self_.load_contacts_from_db().await;
  250. self_.clone().start(sg_root, ex).await;
  251. Ok(Pimpl::DarkIrc(self_))
  252. }
  253. async fn dag_sync(self: Arc<Self>, channel_sub: Subscription<DarkFiResult<ChannelPtr>>) {
  254. i!("Waiting for some P2P connections...");
  255. let mut sync_attempt = 0;
  256. // TODO: these should be configurable
  257. let fast_mode = false;
  258. let mut newest_synced = false;
  259. loop {
  260. let peers_count = self.p2p.peers_count();
  261. self.notify_connect(peers_count, self.event_graph.is_synced()).await;
  262. // Wait until we have enough connections
  263. if peers_count < SYNC_MIN_PEERS {
  264. i!("Connected to {peers_count} peers. Waiting for more connections.");
  265. let conn_sub = self.p2p.hosts().subscribe_channel().await;
  266. loop {
  267. if let Err(err) = conn_sub.receive().await {
  268. w!("Error while waiting for new connections: {err}");
  269. continue
  270. }
  271. if self.p2p.peers_count() >= SYNC_MIN_PEERS {
  272. break
  273. }
  274. }
  275. }
  276. i!("Got peer connection");
  277. sync_attempt += 1;
  278. // Cool off periodically
  279. if sync_attempt > COOLOFF_SYNC_ATTEMPTS {
  280. i!("Wasn't able to sync yet. Cooling off for {COOLOFF_SLEEP_TIME} then will try again.");
  281. sleep(COOLOFF_SLEEP_TIME).await;
  282. sync_attempt = 0;
  283. }
  284. i!("Syncing static DAG");
  285. match self.event_graph.static_sync().await {
  286. Ok(()) => {
  287. i!("Static synced successfully");
  288. // log_memory("after static sync");
  289. }
  290. Err(e) => {
  291. e!("Failed syncing static graph: {e}");
  292. self.p2p.stop().await;
  293. break
  294. }
  295. }
  296. // Sync only the newest DAG first. Older DAGs are caught up in
  297. // the background by `catch_up_sync`, so the `synced` flag (and
  298. // the `connect` notification) is reached after the first DAG
  299. // instead of after all `DAGS_COUNT` of them.
  300. let latest_ts = self.event_graph.current_genesis.read().await.header.timestamp;
  301. i!("Syncing newest event DAG ({latest_ts}) (attempt #{sync_attempt})");
  302. let sync_result = self.sync_dag_slot(latest_ts, fast_mode).await;
  303. match sync_result {
  304. Ok(()) => {
  305. i!(
  306. "Newest event DAG synced successfully ({} mode)",
  307. if fast_mode { "fast" } else { "full" },
  308. );
  309. newest_synced = true;
  310. break
  311. }
  312. Err(e) => {
  313. // TODO: Maybe at this point we should prune or something?
  314. // TODO: Or maybe just tell the user to delete the DAG from FS.
  315. e!("Failed syncing newest DAG ({e}), retrying...");
  316. }
  317. }
  318. }
  319. // The newest DAG is synced: flip the `synced` flag so live `EventPut`
  320. // ingestion and the netstatus UI unblock, then let the remaining older
  321. // DAGs catch up in the background.
  322. if newest_synced {
  323. self.event_graph.synced.store(true, Ordering::Release);
  324. i!("Marked event graph as synced (newest DAG done)");
  325. let self_ = self.clone();
  326. let catch_up_task =
  327. self.ex.clone().spawn(async move { self_.catch_up_sync(fast_mode).await });
  328. self.tasks.lock().push(catch_up_task);
  329. }
  330. let peers_count = self.p2p.peers_count();
  331. self.notify_connect(peers_count, self.event_graph.is_synced()).await;
  332. // Initial sync finished. Now just notify of connection changes
  333. loop {
  334. // Wait for a channel
  335. if let Err(err) = channel_sub.receive().await {
  336. w!("There was an error listening for channels. The service closed unexpectedly with error: {err}");
  337. continue
  338. }
  339. let peers_count = self.p2p.peers_count();
  340. self.notify_connect(peers_count, self.event_graph.is_synced()).await;
  341. }
  342. }
  343. /// Sync a single DAG slot, choosing `dag_sync_headers` (fast) or `dag_sync`
  344. /// (full) per the per-call `fast_mode` flag. Shared by the foreground
  345. /// newest-DAG phase and the background catch-up phase.
  346. async fn sync_dag_slot(&self, dag_ts: u64, fast_mode: bool) -> DarkFiResult<()> {
  347. if fast_mode {
  348. self.event_graph.dag_sync_headers(dag_ts).await
  349. } else {
  350. self.event_graph.dag_sync(dag_ts).await
  351. }
  352. }
  353. /// Background catch-up phase: after the newest DAG has synced (phase 1),
  354. /// walk the remaining DAG slots newest-to-oldest and sync each one.
  355. ///
  356. /// Per-slot failures are retried across rounds with `COOLOFF_SLEEP_TIME`
  357. /// pauses (no give-up), while the walk keeps making progress on the other
  358. /// pending slots. `current_genesis` is re-read before every retry round so
  359. /// rotation mid-catch-up is handled: a newer slot that rotates in is synced
  360. /// first, and a pending slot that rotates out of the retention window is
  361. /// dropped from the walk.
  362. async fn catch_up_sync(self: Arc<Self>, fast_mode: bool) {
  363. i!("Starting background catch-up of older DAGs (newest-to-oldest)");
  364. // The newest slot is already synced by phase 1; collect the rest of the
  365. // retention window below it.
  366. let mut last_newest = self.event_graph.current_genesis.read().await.header.timestamp;
  367. let mut pending: Vec<u64> =
  368. (1..DAGS_COUNT).map(|i| last_newest.saturating_sub(i * HOUR_MS)).collect();
  369. loop {
  370. // Re-read `current_genesis` before each retry round: rotation may
  371. // have shifted the newest slot or dropped a pending slot.
  372. let latest_ts = self.event_graph.current_genesis.read().await.header.timestamp;
  373. // A newer DAG rotated in: it is the new newest slot and must sync
  374. // before the remaining older pending DAGs.
  375. if latest_ts > last_newest {
  376. i!("Syncing newly rotated-in DAG ({latest_ts})");
  377. loop {
  378. match self.sync_dag_slot(latest_ts, fast_mode).await {
  379. Ok(()) => break,
  380. Err(e) => {
  381. e!("Failed syncing newly rotated-in DAG ({latest_ts}): {e}, cooling off");
  382. sleep(COOLOFF_SLEEP_TIME).await;
  383. }
  384. }
  385. }
  386. last_newest = latest_ts;
  387. pending = (1..DAGS_COUNT).map(|i| latest_ts.saturating_sub(i * HOUR_MS)).collect();
  388. }
  389. // Clamp the walk to the retention window: slots rotated out fall
  390. // below the horizon and are skipped rather than retried forever.
  391. let horizon = latest_ts.saturating_sub((DAGS_COUNT - 1) * HOUR_MS);
  392. let mut still_pending = Vec::new();
  393. for dag_ts in pending.iter().copied() {
  394. if dag_ts < horizon {
  395. i!("Skipping rotated-out DAG ({dag_ts})");
  396. continue
  397. }
  398. match self.sync_dag_slot(dag_ts, fast_mode).await {
  399. Ok(()) => {
  400. i!("Synced older DAG ({dag_ts})");
  401. }
  402. Err(e) => {
  403. e!("Failed syncing older DAG ({dag_ts}): {e}, will retry after cooldown");
  404. still_pending.push(dag_ts);
  405. sleep(COOLOFF_SLEEP_TIME).await;
  406. }
  407. }
  408. }
  409. pending = still_pending;
  410. if pending.is_empty() {
  411. i!("Background catch-up complete; all older DAGs synced");
  412. break
  413. }
  414. }
  415. }
  416. /// Send a notification when there's a change in number of peers or the DAG sync status
  417. ///
  418. /// The node is only borrowed to grab the signal, so no strong node reference
  419. /// is held across the trigger below. Otherwise a notify racing `SceneNode::setup`'s
  420. /// strong_count assertion would panic the app.
  421. pub async fn notify_connect(&self, peers_count: usize, is_dag_synced: bool) {
  422. let sig = {
  423. let node = self.node.upgrade().unwrap();
  424. node.get_signal("connect").unwrap()
  425. };
  426. sig.trigger(serialize(&(peers_count as u32, is_dag_synced))).await;
  427. }
  428. /// Update the `outbound_peers` property with the outgoing connection slots addrs.
  429. /// Allows us to monitor the network state of our p2p node.
  430. /// Intermediate states append ` (status)` to the addr.
  431. async fn relay_outbound_slots(dnet_sub: Subscription<DnetEvent>, prop: PropertyPtr) {
  432. loop {
  433. let event = dnet_sub.receive().await;
  434. let (slot, kind, addr) = match event {
  435. DnetEvent::OutboundSlotConnected(info) => {
  436. (info.slot, "connected", Some(info.addr.to_string()))
  437. }
  438. DnetEvent::OutboundSlotConnecting(info) => {
  439. (info.slot, "connecting", Some(info.addr.to_string()))
  440. }
  441. DnetEvent::OutboundSlotDisconnected(info) => (info.slot, "disconnected", None),
  442. DnetEvent::OutboundSlotSleeping(info) => (info.slot, "sleeping", None),
  443. _ => continue,
  444. };
  445. let mut atom = PropertyAtomicGuard::none();
  446. let idx = slot as usize;
  447. assert!(idx < prop.get_len());
  448. match addr {
  449. Some(addr) if kind == "connected" => {
  450. prop.set_str(&mut atom, Role::Internal, idx, addr).unwrap();
  451. }
  452. Some(addr) => {
  453. let val = format!("{addr} ({kind})");
  454. prop.set_str(&mut atom, Role::Internal, idx, val).unwrap();
  455. }
  456. None => prop.set_null(&mut atom, Role::Internal, idx).unwrap(),
  457. }
  458. }
  459. }
  460. async fn relay_events(self: Arc<Self>, ev_sub: Subscription<event_graph::Event>) {
  461. loop {
  462. let ev = ev_sub.receive().await;
  463. // Try to deserialize the `Event`'s content into a `Privmsg`
  464. let privmsg: Privmsg = match deserialize_async(ev.content()).await {
  465. Ok(v) => v,
  466. Err(e) => {
  467. e!("[IRC CLIENT] Failed deserializing incoming Privmsg event: {}", e);
  468. continue
  469. }
  470. };
  471. // Route the message. An already-decrypted (plaintext) message names a
  472. // channel we hold directly: encrypted channels arrive as base58
  473. // ciphertext, so a channel key we recognise is plaintext by definition
  474. // and is accepted as-is. Anything else must decrypt as a channel or DM;
  475. // undecryptable traffic (base58 garbage in neither map) is silently dropped.
  476. let mut privmsg = privmsg;
  477. // Is this a plaintext channel?
  478. let is_plaintext = self.channels.read().await.contains_key(&privmsg.channel);
  479. if !is_plaintext && !self.try_decrypt(&mut privmsg, &self.nick.get()).await {
  480. continue;
  481. }
  482. let mut timest = ev.header.timestamp;
  483. let msg_id = msg_id(&privmsg, timest);
  484. t!(
  485. "Relaying ev_id={:?}, ev={ev:?}, msg_id={msg_id}, privmsg={privmsg:?}, timest={timest}",
  486. ev.id(),
  487. );
  488. let is_self = {
  489. let mut is_self = false;
  490. let mut seen = self.seen_msgs.lock();
  491. match seen.get_status(&msg_id) {
  492. Some(msg) => {
  493. is_self = msg.is_self;
  494. if !msg.is_self || msg.seen_times > 1 {
  495. w!("Skipping duplicate seen message: {msg_id}");
  496. continue
  497. }
  498. }
  499. None => {
  500. seen.push(msg_id.clone(), false);
  501. }
  502. }
  503. is_self
  504. };
  505. // This is a hack to make messages appear sequentially in the UI
  506. let now_timest = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
  507. if !is_self && timest.abs_diff(now_timest) < RECENT_TIME_DIST {
  508. d!("Applied timestamp correction: <{timest}> => <{now_timest}>");
  509. timest = now_timest;
  510. }
  511. // Workaround for the chatview hack. This nick is off limits!
  512. let mut nick = privmsg.nick;
  513. if nick == "NOTICE" {
  514. nick = "noticer".to_string();
  515. }
  516. self.notify_recv(privmsg.channel, timest, msg_id, nick, privmsg.msg).await;
  517. }
  518. }
  519. /// Send a notification about a new received message
  520. pub async fn notify_recv(
  521. &self,
  522. channel: String,
  523. timestamp: Timestamp,
  524. id: MessageId,
  525. nick: String,
  526. msg: String,
  527. ) {
  528. assert!(
  529. channel.starts_with('#') || channel.starts_with('@'),
  530. "notify_recv channel must be a \"#name\" channel or \"@name\" DM, got: {channel}"
  531. );
  532. let mut arg_data = vec![];
  533. channel.encode(&mut arg_data).unwrap();
  534. timestamp.encode(&mut arg_data).unwrap();
  535. id.encode(&mut arg_data).unwrap();
  536. nick.encode(&mut arg_data).unwrap();
  537. msg.encode(&mut arg_data).unwrap();
  538. let sig = {
  539. let node = self.node.upgrade().unwrap();
  540. node.get_signal("recv").unwrap()
  541. };
  542. sig.trigger(arg_data).await;
  543. }
  544. async fn process_send(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  545. let Ok(method_call) = sub.receive().await else {
  546. d!("Event relayer closed");
  547. return false
  548. };
  549. t!("method called: send({method_call:?})");
  550. assert!(method_call.send_res.is_none());
  551. fn decode_data(data: &[u8]) -> std::io::Result<(Timestamp, String, String)> {
  552. let mut cur = Cursor::new(&data);
  553. let timest = Timestamp::decode(&mut cur).unwrap();
  554. let channel = String::decode(&mut cur)?;
  555. let msg = String::decode(&mut cur)?;
  556. Ok((timest, channel, msg))
  557. }
  558. let Ok((timest, channel, msg)) = decode_data(&method_call.data) else {
  559. e!("send() method invalid arg data");
  560. return true
  561. };
  562. let Some(self_) = me.upgrade() else {
  563. // Should not happen
  564. panic!("self destroyed before send_method_task was stopped!");
  565. };
  566. self_.handle_send(timest, channel, msg).await;
  567. true
  568. }
  569. /// User wants to send a msg
  570. async fn handle_send(&self, timest: Timestamp, channel: String, msg: String) {
  571. let nick = self.nick.get();
  572. // Send text to channel
  573. d!("Sending privmsg: {timest} {channel}: <{nick}> {msg}");
  574. let mut msg = Privmsg { version: 0, msg_type: 0, channel, nick, msg };
  575. // DM layers use the "@name" UI id; strip it to the bare contact key and
  576. // require the contact to exist, else refuse to broadcast.
  577. if let Some(bare) = msg.channel.strip_prefix('@').map(str::to_string) {
  578. msg.channel = bare;
  579. if self.try_encrypt_dm(&mut msg).await.is_err() {
  580. e!("Refusing to send DM to unknown contact");
  581. return;
  582. }
  583. } else {
  584. assert!(msg.channel.starts_with('#'), "channel name must start with #");
  585. self.try_encrypt_channel(&mut msg).await;
  586. }
  587. let evgr = self.event_graph.clone();
  588. let event = event_graph::Event::with_timestamp(timest, serialize_async(&msg).await, &evgr)
  589. .await
  590. .unwrap();
  591. let msg_id = msg_id(&msg, timest);
  592. // Keep track of our own messages so we don't apply timestamp correction to them
  593. // which messes up the msg id.
  594. {
  595. let mut seen = self.seen_msgs.lock();
  596. seen.push(msg_id.clone(), true);
  597. }
  598. // Broadcast the msg
  599. let current_genesis = self.event_graph.current_genesis.read().await;
  600. let dag_name = current_genesis.header.timestamp.to_string();
  601. if let Err(e) = evgr.insert_signal_with_blob(&event, &[], &dag_name).await {
  602. e!("Failed inserting new event to DAG: {}", e);
  603. }
  604. if let Err(e) = self.p2p.broadcast(&EventPut(event, vec![])).await {
  605. e!("Event broadcast was not admitted: {e}");
  606. }
  607. }
  608. /// Load channels from the app database and populate encryption keys
  609. pub async fn load_channels_from_db(&self) {
  610. let mut channels = self.channels.write().await;
  611. channels.clear();
  612. let joined: HashSet<String> = read_joined_channels().into_iter().collect();
  613. for ui_channel in self.app_db.channels().await.unwrap() {
  614. let full_name = format!("#{}", ui_channel.name);
  615. if !joined.contains(&full_name) {
  616. continue
  617. }
  618. // Convert to IrcChannel with encryption
  619. let mut irc_channel =
  620. IrcChannel { topic: String::new(), nicks: HashSet::new(), saltbox: None };
  621. if let Some(secret) = ui_channel.secret {
  622. // Convert secret array to SecretKey first, then derive PublicKey
  623. let secret_key = SecretKey::from_bytes(secret);
  624. let public = secret_key.public_key();
  625. let saltbox = ChaChaBox::new(&public, &secret_key);
  626. // The secret in base58 for debugging
  627. //let secret_b58 = bs58::encode(secret).into_string();
  628. irc_channel.saltbox = Some(Arc::new(saltbox));
  629. }
  630. let is_encrypted = irc_channel.saltbox.is_some();
  631. channels.insert(full_name, irc_channel);
  632. i!("Loaded channel: #{} (encrypted: {})", ui_channel.name, is_encrypted);
  633. }
  634. }
  635. /// Load contacts from the app database and build their encryption boxes.
  636. pub async fn load_contacts_from_db(&self) {
  637. let mut contacts = self.contacts.write().await;
  638. contacts.clear();
  639. let joined: HashSet<String> = read_joined_channels().into_iter().collect();
  640. for contact in self.app_db.contacts().await.unwrap() {
  641. if !joined.contains(&format!("@{}", contact.name)) {
  642. continue
  643. }
  644. let their_public = PublicKey::from(contact.public);
  645. let saltbox = Arc::new(ChaChaBox::new(&their_public, &self.dm_secret));
  646. let self_saltbox =
  647. Arc::new(ChaChaBox::new(&self.dm_secret.public_key(), &self.dm_secret));
  648. contacts.insert(contact.name.clone(), IrcContact { saltbox, self_saltbox });
  649. i!("Loaded contact: {}", contact.name);
  650. }
  651. }
  652. async fn rescan_channel_history(self: Arc<Self>, channel: String) {
  653. i!("Starting background rescan for channel: {channel}");
  654. // Fetch and order all events from the DAG (like darkirc does)
  655. let Ok(dag_events) = self.event_graph.order_events().await else {
  656. e!("Failed to fetch events from DAG");
  657. return;
  658. };
  659. let mut found_count = 0;
  660. for event in dag_events.iter() {
  661. // Deserialize Privmsg
  662. let mut privmsg = match deserialize_async::<Privmsg>(event.content()).await {
  663. Ok(pm) => pm,
  664. Err(e) => {
  665. t!("Not a Privmsg event, skipping");
  666. continue;
  667. }
  668. };
  669. // Try to decrypt (handles encrypted channels)
  670. self.try_decrypt(&mut privmsg, &self.nick.get()).await;
  671. // Check if message belongs to target channel
  672. if privmsg.channel != channel {
  673. continue;
  674. }
  675. found_count += 1;
  676. // Calculate message ID
  677. let timest = event.header.timestamp;
  678. let msg_id = msg_id(&privmsg, timest);
  679. // Send to ChatView via notify_recv (handles DB storage and duplicates)
  680. self.notify_recv(
  681. channel.clone(),
  682. timest,
  683. msg_id,
  684. privmsg.nick.clone(),
  685. privmsg.msg.clone(),
  686. )
  687. .await;
  688. }
  689. i!("Rescan complete for {channel}: found {found_count} messages");
  690. }
  691. async fn process_rescan(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  692. let Ok(method_call) = sub.receive().await else {
  693. d!("Rescan method closed");
  694. return false
  695. };
  696. t!("method called: rescan({method_call:?})");
  697. let Some(self_) = me.upgrade() else {
  698. e!("DarkIrc destroyed before rescan completed");
  699. return false
  700. };
  701. // Decode channel name from method data
  702. let mut cur = std::io::Cursor::new(&method_call.data);
  703. let Ok(channel) = String::decode(&mut cur) else {
  704. e!("Rescan method called with invalid channel data");
  705. return false
  706. };
  707. self_.load_channels_from_db().await;
  708. self_.load_contacts_from_db().await;
  709. let task = self_.ex.clone().spawn(self_.clone().rescan_channel_history(channel));
  710. self_.tasks.lock().push(task);
  711. true
  712. }
  713. /// Apply the transport-specific P2P configuration (profiles, seeds,
  714. /// active profiles) using the defaults from darkirc_config.toml
  715. fn apply_transport_settings(settings: &mut NetSettings, transport: &str) {
  716. settings.seeds.clear();
  717. settings.profiles.clear();
  718. settings.active_profiles.clear();
  719. match transport {
  720. "tor" => {
  721. i!("Setup P2P network [tor]");
  722. let mut tor_profile = NetworkProfile::tor_default();
  723. tor_profile.outbound_connect_timeout = 60;
  724. settings.profiles.insert("tor".to_string(), tor_profile);
  725. settings.outbound_peer_discovery_cooloff_time = 60;
  726. settings.seeds.push(
  727. url::Url::parse(
  728. "tor://wgxxaifz5gv4iggcflyl67lgmsihffs6bbwobqah4np52t3y3olrnpid.onion:9601",
  729. )
  730. .unwrap(),
  731. );
  732. settings.seeds.push(
  733. url::Url::parse(
  734. "tor://inx5s3pdzddvgb5ii3oydutmbvw6fvor3oqu65wtxl3pyevtvrdn4had.onion:9601",
  735. )
  736. .unwrap(),
  737. );
  738. settings.active_profiles.push("tor".to_string());
  739. }
  740. "tcp" => {
  741. i!("Setup P2P network [clearnet]");
  742. let mut profile = NetworkProfile::default();
  743. profile.outbound_connect_timeout = 40;
  744. profile.channel_handshake_timeout = 30;
  745. settings.profiles.insert("tcp+tls".to_string(), profile);
  746. settings.seeds.push(url::Url::parse("tcp+tls://lilith0.dark.fi:9600").unwrap());
  747. settings.seeds.push(url::Url::parse("tcp+tls://lilith1.dark.fi:9600").unwrap());
  748. settings.active_profiles.push("tcp+tls".to_string());
  749. }
  750. unhandled => panic!("Unhandled net.transport value: {unhandled}"),
  751. }
  752. }
  753. /// `net.transport` was switched, reconfigure the P2P network and restart it
  754. async fn handle_transport_change(&self, transport: String) {
  755. i!("Transport changed to {transport}, restarting P2P network");
  756. let was_started = self.chat_is_enabled.get();
  757. if was_started {
  758. self.p2p.clone().stop().await;
  759. }
  760. let settings_lock = self.p2p.settings();
  761. let mut settings = settings_lock.write().await;
  762. Self::apply_transport_settings(&mut settings, &transport);
  763. drop(settings);
  764. if was_started {
  765. while let Err(err) = self.p2p.clone().start().await {
  766. e!("Failed to start P2P network: {err}!");
  767. e!("Retrying in {P2P_RETRY_TIME} secs");
  768. sleep(P2P_RETRY_TIME).await;
  769. }
  770. let peers_count = self.p2p.peers_count();
  771. self.notify_connect(peers_count, self.event_graph.is_synced()).await;
  772. }
  773. i!("P2P transport restart completed");
  774. }
  775. /// `chat.is_enabled` was switched on
  776. async fn handle_start(&self) {
  777. i!("Manual P2P start triggered");
  778. while let Err(err) = self.p2p.clone().start().await {
  779. e!("Failed to start P2P network: {err}!");
  780. e!("Retrying in {P2P_RETRY_TIME} secs");
  781. sleep(P2P_RETRY_TIME).await;
  782. }
  783. let peers_count = self.p2p.peers_count();
  784. self.notify_connect(peers_count, self.event_graph.is_synced()).await;
  785. i!("P2P start completed");
  786. }
  787. /// `chat.is_enabled` was switched off
  788. async fn handle_stop(&self) {
  789. i!("Manual P2P stop triggered");
  790. self.p2p.clone().stop().await;
  791. // Stopped outbound slots emit no dnet events, so clear the property here
  792. let node = self.node.upgrade().unwrap();
  793. let prop = node.get_property("outbound_peers").unwrap();
  794. let mut atom = PropertyAtomicGuard::none();
  795. for idx in 0..prop.get_len() {
  796. prop.set_null(&mut atom, Role::Internal, idx).unwrap();
  797. }
  798. self.notify_connect(0, self.event_graph.is_synced()).await;
  799. i!("P2P stop completed");
  800. }
  801. async fn set_outbound_connections(&self, count: usize) {
  802. let p2p_settings = self.p2p.settings();
  803. if p2p_settings.read().await.outbound_connections == count {
  804. return;
  805. }
  806. p2p_settings.write().await.outbound_connections = count;
  807. self.p2p.clone().reload().await;
  808. }
  809. async fn start(self: Arc<Self>, sg_root: SceneNodePtr, ex: ExecutorPtr) {
  810. i!("Registering EventGraph P2P protocol");
  811. let event_graph_ = Arc::clone(&self.event_graph);
  812. let registry = self.p2p.protocol_registry();
  813. registry
  814. .register(SESSION_DEFAULT, move |channel, _| {
  815. let event_graph_ = event_graph_.clone();
  816. async move { ProtocolEventGraph::init(event_graph_, channel).await.unwrap() }
  817. })
  818. .await;
  819. let me = Arc::downgrade(&self);
  820. let node = &self.node.upgrade().unwrap();
  821. let method_sub = node.subscribe_method_call("send").unwrap();
  822. let me2 = me.clone();
  823. let send_method_task =
  824. ex.spawn(async move { while Self::process_send(&me2, &method_sub).await {} });
  825. let chat_is_enabled = self.chat_is_enabled.clone();
  826. let chat_is_enabled_sub = chat_is_enabled.prop().subscribe_modify();
  827. let me2 = me.clone();
  828. let setting_task = ex.spawn(async move {
  829. if chat_is_enabled.get() {
  830. let Some(self_) = me2.upgrade() else { return };
  831. self_.handle_start().await;
  832. }
  833. while let Ok(_) = chat_is_enabled_sub.receive().await {
  834. let Some(self_) = me2.upgrade() else { break };
  835. if chat_is_enabled.get() {
  836. self_.handle_start().await;
  837. } else {
  838. self_.handle_stop().await;
  839. }
  840. }
  841. });
  842. let net_transport = self.net_transport.clone();
  843. let net_transport_sub = net_transport.prop().subscribe_modify();
  844. let me2 = me.clone();
  845. let transport_task = ex.spawn(async move {
  846. while let Ok(_) = net_transport_sub.receive().await {
  847. let Some(self_) = me2.upgrade() else { break };
  848. self_.handle_transport_change(net_transport.get()).await;
  849. }
  850. });
  851. let rescan_method_sub = node.subscribe_method_call("rescan").unwrap();
  852. let me2 = me.clone();
  853. let rescan_method_task =
  854. ex.spawn(async move { while Self::process_rescan(&me2, &rescan_method_sub).await {} });
  855. let mut on_modify = OnModify::new(ex.clone(), self.node.clone(), me.clone());
  856. async fn save_nick(self_: Arc<DarkIrc>, _batch: BatchGuardPtr) {
  857. if let Err(err) = self_.app_db.nick_set(&self_.nick.get()).await {
  858. e!("Failed persisting nick to app db: {err}");
  859. }
  860. }
  861. on_modify.when_change(self.nick.prop(), save_nick);
  862. let ev_sub = self.event_graph.event_subscribe().await;
  863. let ev_task = ex.spawn(self.clone().relay_events(ev_sub));
  864. // Sync the DAG / check sync status
  865. let channel_sub = self.p2p.hosts().subscribe_channel().await;
  866. let dag_task = ex.spawn(self.clone().dag_sync(channel_sub));
  867. let window_node = sg_root.lookup_node("/window").unwrap();
  868. let (start_slot, start_recv) = Slot::new("darkirc_start");
  869. window_node.register("start", start_slot).unwrap();
  870. let me2 = Arc::downgrade(&self);
  871. let start_task = ex.spawn(async move {
  872. while let Ok(_) = start_recv.recv().await {
  873. let Some(self_) = me2.upgrade() else { break };
  874. self_.set_outbound_connections(P2P_OUTBOUND_ACTIVE).await;
  875. }
  876. });
  877. let (stop_slot, stop_recv) = Slot::new("darkirc_stop");
  878. window_node.register("stop", stop_slot).unwrap();
  879. let me2 = Arc::downgrade(&self);
  880. let stop_task = ex.spawn(async move {
  881. while let Ok(_) = stop_recv.recv().await {
  882. let Some(self_) = me2.upgrade() else { break };
  883. self_.set_outbound_connections(P2P_OUTBOUND_SLEEP).await;
  884. }
  885. });
  886. let (screen_changed_slot, screen_changed_recv) = Slot::new("darkirc_screen_changed");
  887. window_node.register("screen_changed", screen_changed_slot).unwrap();
  888. let me2 = Arc::downgrade(&self);
  889. let screen_changed_task = ex.spawn(async move {
  890. while let Ok(data) = screen_changed_recv.recv().await {
  891. let Some(self_) = me2.upgrade() else { break };
  892. let mut cursor = Cursor::new(&data);
  893. let Ok(screen_on) = bool::decode(&mut cursor) else { continue };
  894. if screen_on {
  895. self_.set_outbound_connections(P2P_OUTBOUND_ACTIVE).await;
  896. } else {
  897. self_.set_outbound_connections(P2P_OUTBOUND_SLEEP).await;
  898. }
  899. }
  900. });
  901. let mut tasks = vec![
  902. send_method_task,
  903. setting_task,
  904. transport_task,
  905. rescan_method_task,
  906. ev_task,
  907. dag_task,
  908. start_task,
  909. stop_task,
  910. screen_changed_task,
  911. ];
  912. if DNET_ENABLED {
  913. let dnet_sub = self.p2p.dnet_subscribe().await;
  914. let node = self.node.upgrade().unwrap();
  915. let prop = node.get_property("outbound_peers").unwrap();
  916. let dnet_task = ex.spawn(Self::relay_outbound_slots(dnet_sub, prop));
  917. tasks.push(dnet_task);
  918. }
  919. tasks.append(&mut on_modify.tasks);
  920. *self.tasks.lock() = tasks;
  921. }
  922. /// Encrypt a channel `Privmsg` in place if the channel has a shared key.
  923. /// Open channels with no key are left plaintext.
  924. pub async fn try_encrypt_channel(&self, privmsg: &mut Privmsg) {
  925. let guard = self.channels.read().await;
  926. let Some((name, channel)) = guard.get_key_value(&privmsg.channel) else {
  927. return;
  928. };
  929. let Some(saltbox) = &channel.saltbox else {
  930. return;
  931. };
  932. privmsg.channel = saltbox::encrypt(saltbox, &[0x00; MAX_NICK_LEN]);
  933. privmsg.nick = saltbox::encrypt(saltbox, &pad(&privmsg.nick));
  934. privmsg.msg = saltbox::encrypt(saltbox, privmsg.msg.as_bytes());
  935. d!("Successfully encrypted message for {name}");
  936. }
  937. /// Encrypt a DM `Privmsg` in place for the contact named by `privmsg.channel`
  938. /// (the bare key, with no leading "@"). Fails if the contact is unknown so
  939. /// the caller can refuse to broadcast a message no one could decrypt.
  940. pub async fn try_encrypt_dm(&self, privmsg: &mut Privmsg) -> Result<()> {
  941. let guard = self.contacts.read().await;
  942. let Some((name, contact)) = guard.get_key_value(&privmsg.channel) else {
  943. return Err(Error::ContactNotFound);
  944. };
  945. privmsg.channel = saltbox::encrypt(&contact.saltbox, &[0x00; MAX_NICK_LEN]);
  946. privmsg.nick = saltbox::encrypt(&contact.self_saltbox, &[0x00; MAX_NICK_LEN]);
  947. privmsg.msg = saltbox::encrypt(&contact.saltbox, privmsg.msg.as_bytes());
  948. d!("Successfully encrypted DM for {name}");
  949. Ok(())
  950. }
  951. /// Try decrypting a `Privmsg` as a channel message in place. Returns true on
  952. /// success. Plaintext messages for a known keyless channel are accepted
  953. /// as-is; everything else returns false.
  954. pub async fn try_decrypt_channel(&self, privmsg: &mut Privmsg) -> bool {
  955. let Ok(channel_ciphertext) = bs58::decode(&privmsg.channel).into_vec() else {
  956. // Not encrypted: accept only if it names a channel we hold.
  957. return self.channels.read().await.contains_key(&privmsg.channel);
  958. };
  959. let Ok(nick_ciphertext) = bs58::decode(&privmsg.nick).into_vec() else { return false };
  960. let Ok(msg_ciphertext) = bs58::decode(&privmsg.msg).into_vec() else { return false };
  961. for (name, channel) in self.channels.read().await.iter() {
  962. let Some(saltbox) = &channel.saltbox else { continue };
  963. if saltbox::try_decrypt(saltbox, &channel_ciphertext).is_none() {
  964. continue
  965. };
  966. let Some(mut nick_dec) = saltbox::try_decrypt(saltbox, &nick_ciphertext) else {
  967. w!("Could not decrypt nick ciphertext for channel: {name}");
  968. continue
  969. };
  970. let Some(msg_dec) = saltbox::try_decrypt(saltbox, &msg_ciphertext) else {
  971. w!("Could not decrypt message ciphertext for channel: {name}");
  972. continue
  973. };
  974. unpad(&mut nick_dec);
  975. privmsg.channel = name.to_string();
  976. privmsg.nick = String::from_utf8_lossy(&nick_dec).into();
  977. privmsg.msg = String::from_utf8_lossy(&msg_dec).into();
  978. d!("Successfully decrypted message for {name}");
  979. return true
  980. }
  981. false
  982. }
  983. /// Try decrypting a `Privmsg` as a DM in place. Returns true on success.
  984. pub async fn try_decrypt_contact(&self, privmsg: &mut Privmsg, self_nickname: &str) -> bool {
  985. let Ok(channel_ciphertext) = bs58::decode(&privmsg.channel).into_vec() else {
  986. return false
  987. };
  988. let Ok(nick_ciphertext) = bs58::decode(&privmsg.nick).into_vec() else { return false };
  989. let Ok(msg_ciphertext) = bs58::decode(&privmsg.msg).into_vec() else { return false };
  990. for (name, contact) in self.contacts.read().await.iter() {
  991. if saltbox::try_decrypt(&contact.saltbox, &channel_ciphertext).is_none() {
  992. continue
  993. };
  994. let nick = if saltbox::try_decrypt(&contact.self_saltbox, &nick_ciphertext).is_some() {
  995. String::from(self_nickname)
  996. } else {
  997. name.to_string()
  998. };
  999. let Some(msg_dec) = saltbox::try_decrypt(&contact.saltbox, &msg_ciphertext) else {
  1000. w!("Could not decrypt message ciphertext for contact: {name}");
  1001. continue
  1002. };
  1003. privmsg.channel = format!("@{}", name);
  1004. privmsg.nick = nick;
  1005. privmsg.msg = String::from_utf8_lossy(&msg_dec).into();
  1006. return true
  1007. }
  1008. false
  1009. }
  1010. /// Try decrypting a given potentially encrypted `Privmsg` object as a channel
  1011. /// and then as a DM. Returns true if either succeeded.
  1012. pub async fn try_decrypt(&self, privmsg: &mut Privmsg, self_nickname: &str) -> bool {
  1013. self.try_decrypt_channel(privmsg).await ||
  1014. self.try_decrypt_contact(privmsg, self_nickname).await
  1015. }
  1016. }
  1017. pub fn msg_id(privmsg: &Privmsg, timest: u64) -> MessageId {
  1018. let mut hasher = blake3::Hasher::new();
  1019. 0u8.encode(&mut hasher).unwrap();
  1020. 0u8.encode(&mut hasher).unwrap();
  1021. timest.encode(&mut hasher).unwrap();
  1022. privmsg.channel.encode(&mut hasher).unwrap();
  1023. privmsg.nick.encode(&mut hasher).unwrap();
  1024. privmsg.msg.encode(&mut hasher).unwrap();
  1025. MessageId(hasher.finalize().into())
  1026. }