freechains/
lib.rs

1//! The `freechains` module implements freechains client utilities for
2//! [Freechains server](https://github.com/Freechains) version `v0.9.0`.
3//!
4//! Main use comes from [Client] struct.
5//!
6//! # Examples
7//!
8//! List all server chains.
9//!
10//! ```no_run
11//! use freechains::{Client, ClientError};
12//!
13//! # fn main() -> Result<(), ClientError> {
14//! let client = Client::new("0.0.0.0:8300");
15//! let chain_ids = client.chains()?;
16//! # Ok(())
17//! # }
18//! ```
19//!
20//! Join and post on a public chain.
21//!
22//! ```no_run
23//! use freechains::{Client, ChainId, ClientError};
24//!
25//! # fn main() -> Result<(), ClientError> {
26//! let client = Client::new("0.0.0.0:8300");
27//!
28//! // Join public chain
29//! let chain_id = ChainId::new("#forum")?;
30//! # let chain_pubkey1 = "";
31//! # let chain_pubkey2 = "";
32//! client.join_chain(&chain_id, &[chain_pubkey1, chain_pubkey2])?;
33//!
34//! // Generate public and private keys
35//! let (pubkey, pvtkey) = client.crypto_pubpvt("strong_password")?;
36//!
37//! let chain_client = client.chain(&chain_id);
38//!
39//! // Post on public chain
40//! chain_client.post(Some(&pvtkey), false, b"Hello, forum!")?;
41//! # Ok(())
42//! # }
43//! ```
44
45#![warn(missing_docs)]
46
47use serde::{Deserialize, Serialize};
48
49use std::convert::TryFrom;
50use std::error::Error;
51use std::fmt;
52use std::io::prelude::*;
53use std::io::{self, BufReader};
54use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
55use std::num;
56use std::sync::mpsc::{self, Receiver};
57use std::thread;
58
59/// Freechains host version supported.
60pub const HOST_VERSION: (u8, u8, u8) = (0, 9, 0);
61
62/// A trait for objects that implements [Read] and [Write].
63pub trait ReadWrite: Read + Write {}
64
65impl ReadWrite for TcpStream {}
66
67/// A trait for objects that can connect to a [ReadWrite] stream.
68pub trait Connect: fmt::Debug {
69    /// Connects to [ReadWrite] stream.
70    fn connect(&self) -> io::Result<Box<dyn ReadWrite>>;
71}
72
73impl Connect for str {
74    fn connect(&self) -> io::Result<Box<dyn ReadWrite>> {
75        Ok(Box::new(TcpStream::connect(self)?))
76    }
77}
78
79impl Connect for &str {
80    fn connect(&self) -> io::Result<Box<dyn ReadWrite>> {
81        Ok(Box::new(TcpStream::connect(self)?))
82    }
83}
84
85/// Connector which uses [TcpStream] as underlying stream.
86#[derive(Debug)]
87pub struct TcpStreamConnector<T>
88where
89    T: fmt::Debug,
90{
91    addrs: T,
92}
93
94impl<T> TcpStreamConnector<T>
95where
96    T: ToSocketAddrs + fmt::Debug,
97{
98    /// Creates new [TcpStreamConnector].
99    pub fn new(addrs: T) -> TcpStreamConnector<T> {
100        TcpStreamConnector { addrs }
101    }
102}
103
104impl<T> Connect for TcpStreamConnector<T>
105where
106    T: ToSocketAddrs + fmt::Debug,
107{
108    fn connect(&self) -> io::Result<Box<dyn ReadWrite>> {
109        let stream = TcpStream::connect(&self.addrs)?;
110        Ok(Box::new(stream))
111    }
112}
113
114/// Freechains client. For more usage examples, check [module documentation](self),
115///
116/// Due to freechains server limitations, a new TCP connection is opened for each client request.
117///
118/// # Examples
119///
120/// Create client from [str].
121///
122/// ```
123/// use freechains::Client;
124///
125/// let client = Client::new("0.0.0.0:8330");
126/// ```
127#[derive(Debug)]
128pub struct Client<T> {
129    connector: T,
130}
131
132impl<T> Client<T> {
133    /// Creates a freechains client.
134    pub fn new(connector: T) -> Client<T> {
135        Client { connector }
136    }
137
138    fn preamble(&self) -> String {
139        format!(
140            "FC v{}.{}.{}",
141            HOST_VERSION.0, HOST_VERSION.1, HOST_VERSION.2
142        )
143    }
144}
145
146impl<T> Client<T>
147where
148    T: Connect,
149{
150    /// Requests freechains server for a symmetric encryption for password `pwd`.
151    pub fn crypto_shared(&self, pwd: &str) -> Result<String, ClientError> {
152        let mut stream = self.connector.connect()?;
153
154        writeln!(stream, "{} crypto shared", self.preamble())?;
155        writeln!(stream, "{}", pwd)?;
156
157        let r = BufReader::new(stream);
158        let line = read_utf8_line(r)?;
159        parse_server_message(&line)
160    }
161
162    /// Requests freechains server to generate public and private key with password `pwd`.
163    pub fn crypto_pubpvt(&self, pwd: &str) -> Result<(String, String), ClientError> {
164        let mut stream = self.connector.connect()?;
165
166        writeln!(stream, "{} crypto pubpvt", self.preamble())?;
167        writeln!(stream, "{}", pwd)?;
168
169        let r = BufReader::new(stream);
170        let line = read_utf8_line(r)?;
171        let line = parse_server_message(&line)?;
172
173        let mut split_line = line.split(" ");
174        let pubkey = String::from(split_line.next().ok_or(ClientError::ExecutionError(
175            String::from("missing public key"),
176        ))?);
177        let prvkey = String::from(split_line.next().ok_or(ClientError::ExecutionError(
178            String::from("missing private key"),
179        ))?);
180
181        Ok((pubkey, prvkey))
182    }
183
184    /// Requests freechains server for a list of subscribed chains.
185    pub fn chains(&self) -> Result<ChainsIds, ClientError> {
186        let mut stream = self.connector.connect()?;
187
188        writeln!(stream, "{} chains list", self.preamble())?;
189
190        let r = BufReader::new(stream);
191        let line = read_utf8_line(r)?;
192        let line = parse_server_message(&line)?;
193
194        let chains: Result<Vec<_>, _> = line.split(' ').map(ChainId::new).collect();
195        let chains = chains?;
196
197        Ok(chains)
198    }
199
200    /// Requests freechains server to join private chain with creators defined by keys.
201    ///
202    /// Returns created chain hash.
203    pub fn join_chain(&self, chain: &ChainId, keys: &[&str]) -> Result<String, ClientError> {
204        let mut stream = self.connector.connect()?;
205
206        writeln!(
207            stream,
208            "{} chains join {} {}",
209            self.preamble(),
210            chain,
211            keys.join(" ")
212        )?;
213
214        let r = BufReader::new(stream);
215        let line = read_utf8_line(r)?;
216        let hash = parse_server_message(&line)?;
217
218        Ok(hash)
219    }
220
221    /// Gets freechains chain client.
222    pub fn chain(&self, chain: &ChainId) -> ChainClient<T> {
223        ChainClient::new(self, &chain.to_string())
224    }
225
226    /// Gets freechains peer client.
227    pub fn peer(&self, peer: impl ToSocketAddrs) -> io::Result<PeerClient<T>> {
228        PeerClient::new(self, peer)
229    }
230
231    /// Gets freechains host client.
232    pub fn host(&self) -> HostClient<T> {
233        HostClient::new(self)
234    }
235}
236
237impl<T> Client<T>
238where
239    T: 'static + Connect + Send + Clone,
240{
241    /// Requests freechains server to notify when any chain has been modified.
242    ///
243    /// Returns [Receiver] thet emits the number of posts and the [ChainId] of the modified chain.
244    ///
245    /// # Examples
246    ///
247    /// Receive a chain update.
248    ///
249    /// ```no_run
250    /// # use std::error::Error;
251    /// # use std::time::Duration;
252    /// use freechains::Client;
253    ///
254    /// # fn main() -> Result<(), Box<dyn Error>> {
255    /// let client = Client::new("host:8330");
256    ///
257    /// let listener = client.listen();
258    ///
259    /// let (updates, chain) = listener.recv_timeout(Duration::from_millis(100))??;
260    /// # Ok(())
261    /// # }
262    /// ```
263    ///
264    /// Keep listening to server.
265    ///
266    /// ```no_run
267    /// # use std::error::Error;
268    /// use freechains::Client;
269    ///
270    /// # fn main() -> Result<(), Box<dyn Error>> {
271    /// let client = Client::new("host:8330");
272    ///
273    /// let listener = client.listen();
274    ///
275    /// for recv in listener {
276    ///     let (updates, chain) = recv?;
277    /// }
278    /// # Ok(())
279    /// # }
280    /// ```
281    pub fn listen(&self) -> Receiver<Result<(usize, ChainId), ClientError>> {
282        let (tx, rx) = mpsc::channel();
283
284        let connector = self.connector.clone();
285        let preamble = self.preamble();
286        thread::spawn(move || match connector.connect() {
287            Err(e) => tx.send(Err(ClientError::IoError(e))),
288            Ok(mut stream) => match writeln!(stream, "{} chains listen", preamble) {
289                Err(e) => tx.send(Err(ClientError::IoError(e))),
290                Ok(_) => {
291                    let r = BufReader::new(stream);
292                    for line in r.lines() {
293                        match line {
294                            Err(e) => {
295                                tx.send(Err(ClientError::IoError(e)))?;
296                                break;
297                            }
298                            Ok(line) => {
299                                let line = match parse_server_message(&line) {
300                                    Err(e) => {
301                                        tx.send(Err(e))?;
302                                        continue;
303                                    }
304                                    Ok(line) => line,
305                                };
306
307                                let mut line_split = line.split_whitespace();
308
309                                let updated = match line_split.next() {
310                                    None => {
311                                        tx.send(Err(ClientError::InvalidServerResponseError(
312                                            String::from("empty updated number"),
313                                        )))?;
314                                        continue;
315                                    }
316                                    Some(updated) => updated,
317                                };
318                                let updated = match updated.parse() {
319                                    Err(e) => {
320                                        tx.send(Err(ClientError::InvalidServerResponseError(
321                                            format!("invalid updated number: {}", e),
322                                        )))?;
323                                        continue;
324                                    }
325                                    Ok(updated) => updated,
326                                };
327
328                                let chain_id = match line_split.next() {
329                                    None => {
330                                        tx.send(Err(ClientError::InvalidServerResponseError(
331                                            String::from("empty chain name"),
332                                        )))?;
333                                        continue;
334                                    }
335                                    Some(id) => id,
336                                };
337                                let chain_id = match ChainId::new(chain_id) {
338                                    Err(e) => {
339                                        tx.send(Err(ClientError::InvalidServerResponseError(
340                                            format!("invalid chain name: {}", e),
341                                        )))?;
342                                        continue;
343                                    }
344                                    Ok(chain_id) => chain_id,
345                                };
346
347                                tx.send(Ok((updated, chain_id)))?;
348                            }
349                        }
350                    }
351
352                    Ok(())
353                }
354            },
355        });
356
357        rx
358    }
359}
360
361/// Freechains chain client actions. Must be created from [Client].
362///
363/// # Examples
364///
365/// Opens '$chat' chain client and gets it's genesis block hash.
366///
367/// ```no_run
368/// # use freechains::{Client, ChainId, ClientError};
369///
370/// # fn main() -> Result<(), ClientError> {
371/// let client = Client::new("0.0.0.0:8300");
372/// let chain_id = ChainId::new("$chat")?;
373///
374/// let client = client.chain(&chain_id);
375/// let genesis_hash = client.genesis();
376///
377/// # Ok(())
378/// # }
379/// ```
380pub struct ChainClient<'a, T> {
381    client: &'a Client<T>,
382    name: String,
383}
384
385impl<'a, T> ChainClient<'a, T> {
386    fn new(client: &'a Client<T>, name: &str) -> ChainClient<'a, T> {
387        let name = String::from(name);
388        ChainClient { client, name }
389    }
390
391    fn preamble(&self) -> String {
392        format!("{} chain {}", self.client.preamble(), self.name)
393    }
394}
395
396impl<'a, T> ChainClient<'a, T>
397where
398    T: Connect,
399{
400    /// Returns chain name.
401    pub fn name(&self) -> &str {
402        &self.name
403    }
404
405    /// Requests freechains server to leave chain.
406    pub fn leave(self) -> Result<bool, ClientError> {
407        let mut stream = self.client.connector.connect()?;
408
409        writeln!(
410            stream,
411            "{} chains leave {}",
412            self.client.preamble(),
413            self.name
414        )?;
415
416        let r = BufReader::new(stream);
417        let line = read_utf8_line(r)?;
418        let line = parse_server_message(&line)?;
419        let left = line == "true";
420
421        Ok(left)
422    }
423
424    /// Requests freechains for the hash of genesis.
425    pub fn genesis(&self) -> Result<String, ClientError> {
426        let mut stream = self.client.connector.connect()?;
427
428        writeln!(stream, "{} genesis", self.preamble())?;
429
430        let r = BufReader::new(stream);
431        let line = read_utf8_line(r)?;
432        parse_server_message(&line)
433    }
434
435    /// Requests freechains server for a payload for the specified post. Post must be identified by
436    /// its hash.
437    pub fn payload(&self, hash: &str, pvtkey: Option<&str>) -> Result<Vec<u8>, ClientError> {
438        let mut stream = self.client.connector.connect()?;
439
440        let pvtkey = pvtkey.unwrap_or("null");
441        writeln!(
442            stream,
443            "{} get payload {} {}",
444            self.preamble(),
445            hash,
446            pvtkey
447        )?;
448
449        let mut r = BufReader::new(stream);
450        let payload_size = read_utf8_line(&mut r)?;
451        let payload_size = parse_server_message(&payload_size)?;
452        let payload_size = payload_size.parse()?;
453
454        let mut buf = vec![0; payload_size];
455        r.read_exact(&mut buf)?;
456
457        Ok(buf.to_vec())
458    }
459
460    /// Requests freechains server for a block of content. Content must be identified by its hash.
461    pub fn content(&self, hash: &str, pvtkey: Option<&str>) -> Result<ContentBlock, ClientError> {
462        let mut stream = self.client.connector.connect()?;
463
464        let pvtkey = pvtkey.unwrap_or("null");
465        writeln!(stream, "{} get block {} {}", self.preamble(), hash, pvtkey)?;
466
467        let mut r = BufReader::new(stream);
468        let block_size = read_utf8_line(&mut r)?;
469        let block_size = parse_server_message(&block_size)?;
470        let block_size = block_size.parse()?;
471
472        let mut buf = vec![0; block_size];
473        r.read_exact(&mut buf)?;
474
475        let content: ContentBlock = serde_json::from_slice(&buf)?;
476
477        Ok(content)
478    }
479
480    /// Requests freechains server to post a message.
481    ///
482    /// Returns message hash.
483    pub fn post(
484        &self,
485        signature: Option<&str>,
486        encrypt: bool,
487        payload: &[u8],
488    ) -> Result<String, ClientError> {
489        let mut stream = self.client.connector.connect()?;
490
491        let signature = signature.unwrap_or("anon");
492        writeln!(
493            stream,
494            "{} post {} {} {}",
495            self.preamble(),
496            signature,
497            encrypt,
498            payload.len()
499        )?;
500
501        stream.write_all(payload)?;
502
503        let r = BufReader::new(stream);
504        let line = read_utf8_line(r)?;
505        parse_server_message(&line)
506    }
507
508    /// Requests freechains server to get chain heads.
509    pub fn heads(&self, blocked: bool) -> Result<Vec<String>, ClientError> {
510        let mut stream = self.client.connector.connect()?;
511
512        let mut msg = String::from("heads");
513        if blocked {
514            msg = format!("{} blocked", msg);
515        }
516        writeln!(stream, "{} {}", self.preamble(), &msg)?;
517
518        let r = BufReader::new(stream);
519        let line = read_utf8_line(r)?;
520        let response = parse_server_message(&line)?;
521        let hashes = response.split(' ').map(String::from).collect();
522
523        Ok(hashes)
524    }
525
526    /// Requests freechains server to return its consensus chain.
527    pub fn consensus(&self) -> Result<Vec<String>, ClientError> {
528        let mut stream = self.client.connector.connect()?;
529
530        writeln!(stream, "{} consensus", self.preamble(),)?;
531
532        let r = BufReader::new(stream);
533        let line = read_utf8_line(r)?;
534        let response = parse_server_message(&line)?;
535        let hashes = response.split(' ').map(String::from).collect();
536
537        Ok(hashes)
538    }
539
540    /// Requests freechains server for content reputation.
541    ///
542    /// Accepts either a post hash, or an user public key.
543    pub fn reputation(&self, hash: &str) -> Result<isize, ClientError> {
544        let mut stream = self.client.connector.connect()?;
545
546        writeln!(stream, "{} reps {}", self.preamble(), hash)?;
547
548        let r = BufReader::new(stream);
549        let line = read_utf8_line(r)?;
550        let response = parse_server_message(&line)?;
551        let reputation = response.parse()?;
552
553        Ok(reputation)
554    }
555
556    /// Requests freechains server to give content a like.
557    pub fn like(&self, hash: &str, pvtkey: &str, reason: &[u8]) -> Result<String, ClientError> {
558        let mut stream = self.client.connector.connect()?;
559
560        writeln!(
561            stream,
562            "{} like 1 {} {} {}",
563            self.preamble(),
564            hash,
565            pvtkey,
566            reason.len()
567        )?;
568        stream.write_all(reason)?;
569
570        let r = BufReader::new(stream);
571        let line = read_utf8_line(r)?;
572        parse_server_message(&line)
573    }
574
575    /// Requests freechains server to give content a dislike.
576    pub fn dislike(&self, hash: &str, pvtkey: &str, reason: &[u8]) -> Result<String, ClientError> {
577        let mut stream = self.client.connector.connect()?;
578
579        writeln!(
580            stream,
581            "{} like -1 {} {} {}",
582            self.preamble(),
583            hash,
584            pvtkey,
585            reason.len()
586        )?;
587        stream.write_all(reason)?;
588
589        let r = BufReader::new(stream);
590        let line = read_utf8_line(r)?;
591        parse_server_message(&line)
592    }
593}
594
595impl<T> ChainClient<'_, T>
596where
597    T: 'static + Connect + Clone + Send,
598{
599    /// Requests freechains server to notify when the chain has been modified.
600    ///
601    /// Returns [Receiver] thet emits the number of posts modified on the chain.
602    ///
603    /// # Examples
604    ///
605    /// Receive a chain update.
606    ///
607    /// ```no_run
608    /// # use std::error::Error;
609    /// # use std::time::Duration;
610    /// use freechains::{Client, ChainId};
611    ///
612    /// # fn main() -> Result<(), Box<dyn Error>> {
613    /// let client = Client::new("host:8330");
614    ///
615    /// let chain_id = ChainId::new("#forum")?;
616    /// let listener = client.chain(&chain_id).listen();
617    ///
618    /// let updates = listener.recv_timeout(Duration::from_millis(100))??;
619    /// # Ok(())
620    /// # }
621    /// ```
622    ///
623    /// Keep listening to chain changes.
624    ///
625    /// ```no_run
626    /// # use std::error::Error;
627    /// use freechains::{Client, ChainId};
628    ///
629    /// # fn main() -> Result<(), Box<dyn Error>> {
630    /// let client = Client::new("host:8330");
631    ///
632    /// let chain_id = ChainId::new("#forum")?;
633    /// let listener = client.chain(&chain_id).listen();
634    ///
635    /// for recv in listener {
636    ///     let updates = recv?;
637    /// }
638    /// # Ok(())
639    /// # }
640    /// ```
641    pub fn listen(&self) -> Receiver<Result<usize, ClientError>> {
642        let (tx, rx) = mpsc::channel();
643
644        let connector = self.client.connector.clone();
645        let preamble = self.preamble();
646        thread::spawn(move || match connector.connect() {
647            Err(e) => tx.send(Err(ClientError::IoError(e))),
648            Ok(mut stream) => match writeln!(stream, "{} listen", preamble) {
649                Err(e) => tx.send(Err(ClientError::IoError(e))),
650                Ok(_) => {
651                    let r = BufReader::new(stream);
652                    for line in r.lines() {
653                        match line {
654                            Err(e) => {
655                                tx.send(Err(ClientError::IoError(e)))?;
656                                break;
657                            }
658                            Ok(line) => {
659                                let line = match parse_server_message(&line) {
660                                    Err(e) => {
661                                        tx.send(Err(e))?;
662                                        continue;
663                                    }
664                                    Ok(line) => line,
665                                };
666
667                                let updated = match line.parse() {
668                                    Err(e) => {
669                                        tx.send(Err(ClientError::InvalidServerResponseError(
670                                            format!("invalid updated number: {}", e),
671                                        )))?;
672                                        continue;
673                                    }
674                                    Ok(updated) => updated,
675                                };
676
677                                tx.send(Ok(updated))?;
678                            }
679                        }
680                    }
681
682                    Ok(())
683                }
684            },
685        });
686
687        rx
688    }
689}
690
691impl<T> fmt::Display for ChainClient<'_, T> {
692    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
693        self.name.fmt(f)
694    }
695}
696
697impl<T> fmt::Debug for ChainClient<'_, T> {
698    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
699        self.name.fmt(f)
700    }
701}
702
703/// Freechains peer client actions. Must be created from [Client].
704///
705/// # Examples
706///
707/// Opens peer client from local server `host1:8330` to remote server `host2:8330` and lists it's chains.
708///
709/// ```no_run
710/// use freechains::{Client, ClientError};
711///
712/// # fn main() -> Result<(), ClientError> {
713/// let client = Client::new("host1:8330");
714/// let client = client.peer("host2:8330")?;
715/// let chains = client.chains()?;
716///
717/// # Ok(())
718/// # }
719/// ```
720pub struct PeerClient<'a, T> {
721    client: &'a Client<T>,
722    peer: SocketAddr,
723}
724
725impl<'a, T> PeerClient<'a, T>
726where
727    T: Connect,
728{
729    fn new(client: &'a Client<T>, peer: impl ToSocketAddrs) -> io::Result<PeerClient<'a, T>> {
730        let mut peer = peer.to_socket_addrs()?;
731        let peer = peer.next().unwrap_or("0.0.0.0:8330".parse().unwrap());
732
733        Ok(PeerClient { client, peer })
734    }
735
736    fn preamble(&self) -> String {
737        format!(
738            "{} peer {}:{}",
739            self.client.preamble(),
740            self.peer.ip(),
741            self.peer.port()
742        )
743    }
744
745    /// Requests freechains server to send chain to other freechains peer.
746    pub fn send_chain(&self, id: &ChainId) -> Result<(usize, usize), ClientError> {
747        let mut stream = self.client.connector.connect()?;
748
749        writeln!(stream, "{} send {}", self.preamble(), id)?;
750
751        let r = BufReader::new(stream);
752        let line = read_utf8_line(r)?;
753        let response = parse_server_message(&line)?;
754
755        let mut response_split = response.split('/');
756        let sent = response_split
757            .next()
758            .ok_or(ClientError::InvalidServerResponseError(response.clone()))?;
759        let sent = sent.trim().parse()?;
760        let total = response_split
761            .next()
762            .ok_or(ClientError::InvalidServerResponseError(response.clone()))?;
763        let total = total.trim().parse()?;
764
765        Ok((sent, total))
766    }
767
768    /// Requests freechains server to receive chain from other freechains peer.
769    pub fn receive_chain(&self, id: &ChainId) -> Result<(usize, usize), ClientError> {
770        let mut stream = self.client.connector.connect()?;
771
772        writeln!(stream, "{} recv {}", self.preamble(), id)?;
773
774        let r = BufReader::new(stream);
775        let line = read_utf8_line(r)?;
776        let response = parse_server_message(&line)?;
777
778        let mut response_split = response.split('/');
779        let recv = response_split
780            .next()
781            .ok_or(ClientError::InvalidServerResponseError(response.clone()))?;
782        let recv = recv.trim().parse()?;
783        let total = response_split
784            .next()
785            .ok_or(ClientError::InvalidServerResponseError(response.clone()))?;
786        let total = total.trim().parse()?;
787
788        Ok((recv, total))
789    }
790
791    /// Requests freechains server to ping other freechains peer.
792    ///
793    /// Returns ping time in milliseconds.
794    pub fn ping(&self) -> Result<usize, ClientError> {
795        let mut stream = self.client.connector.connect()?;
796
797        writeln!(stream, "{} ping", self.preamble())?;
798
799        let r = BufReader::new(stream);
800        let line = read_utf8_line(r)?;
801        let response = parse_server_message(&line)?;
802        let ping = response.parse()?;
803
804        Ok(ping)
805    }
806
807    /// Requests freechains server to request other freechains peer for their chains.
808    pub fn chains(&self) -> Result<ChainsIds, ClientError> {
809        let mut stream = self.client.connector.connect()?;
810
811        writeln!(stream, "{} chains", self.preamble())?;
812
813        let r = BufReader::new(stream);
814        let line = read_utf8_line(r)?;
815        let response = parse_server_message(&line)?;
816        let chains: Result<Vec<_>, _> = response.split(' ').map(ChainId::new).collect();
817        let chains = chains?;
818
819        Ok(chains)
820    }
821}
822
823/// Freechains host client actions. Must be created from [Client].
824///
825/// # Examples
826///
827/// Opens host client from server `host:8330` and gets server time in milliseconds from Epoch.
828///
829/// ```no_run
830/// use freechains::{Client, ClientError};
831///
832/// # fn main() -> Result<(), ClientError> {
833/// let client = Client::new("host:8330");
834/// let client = client.host();
835/// let time = client.time()?;
836///
837/// # Ok(())
838/// # }
839/// ```
840pub struct HostClient<'a, T> {
841    client: &'a Client<T>,
842}
843
844impl<'a, T> HostClient<'a, T>
845where
846    T: Connect,
847{
848    fn new(client: &'a Client<T>) -> HostClient<'a, T> {
849        HostClient { client }
850    }
851
852    fn preamble(&self) -> String {
853        format!("{} host", self.client.preamble())
854    }
855
856    /// Requests freechains server its internal timer.
857    ///
858    /// Returns milliseconds from Epoch time.
859    pub fn time(&self) -> Result<usize, ClientError> {
860        let mut stream = self.client.connector.connect()?;
861
862        writeln!(stream, "{} now", self.preamble())?;
863
864        let r = BufReader::new(stream);
865        let line = read_utf8_line(r)?;
866        let response = parse_server_message(&line)?;
867        let time = response.parse()?;
868
869        Ok(time)
870    }
871
872    /// Requests freechains server to change its internal timer.
873    ///
874    /// `milli` is the time to be set on the server, as milliseconds from Epoch time.
875    ///
876    /// # Examples
877    ///
878    /// Set server time to 1970-01-01T00:00.0Z.
879    ///
880    /// ```no_run
881    /// use freechains::{Client, ClientError};
882    ///
883    /// # fn main() -> Result<(), ClientError> {
884    /// let client = Client::new("host:8330");
885    /// let client = client.host();
886    /// let time = client.set_time(0)?;
887    ///
888    /// # Ok(())
889    /// # }
890    ///
891    /// ```
892    pub fn set_time(&self, milli: usize) -> Result<usize, ClientError> {
893        let mut stream = self.client.connector.connect()?;
894
895        writeln!(stream, "{} now {}", self.preamble(), milli)?;
896
897        let r = BufReader::new(stream);
898        let line = read_utf8_line(r)?;
899        let response = parse_server_message(&line)?;
900        let time = response.parse()?;
901
902        Ok(time)
903    }
904
905    /// Requests freechains server path.
906    pub fn path(&self) -> Result<String, ClientError> {
907        let mut stream = self.client.connector.connect()?;
908
909        writeln!(stream, "{} path", self.preamble())?;
910
911        let r = BufReader::new(stream);
912        let line = read_utf8_line(r)?;
913        let path = parse_server_message(&line)?;
914
915        Ok(path)
916    }
917
918    /// Requests freechains server to stop itself.
919    pub fn stop(self) -> Result<bool, ClientError> {
920        let mut stream = self.client.connector.connect()?;
921
922        writeln!(stream, "{} stop", self.preamble())?;
923
924        let r = BufReader::new(stream);
925        let line = read_utf8_line(r)?;
926        let stopped = parse_server_message(&line)?;
927        let stopped = stopped == "true";
928
929        Ok(stopped)
930    }
931}
932
933/// Type of freechains chain.
934#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
935pub enum ChainType {
936    /// Private chain. Identifier starts with `$`.
937    PrivateChain,
938
939    /// Public chain. Identifier starts with `#`.
940    PublicChain,
941
942    /// Public Identity chain. Identifier starts with `@`.
943    PublicIdentityChain,
944}
945
946impl fmt::Display for ChainType {
947    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
948        let c: char = self.into();
949        write!(f, "{}", c)
950    }
951}
952
953impl TryFrom<char> for ChainType {
954    type Error = InvalidChainNameError;
955
956    fn try_from(c: char) -> Result<Self, Self::Error> {
957        match c {
958            '$' => Ok(ChainType::PrivateChain),
959            '#' => Ok(ChainType::PublicChain),
960            '@' => Ok(ChainType::PublicIdentityChain),
961            _ => Err(InvalidChainNameError),
962        }
963    }
964}
965
966impl From<&ChainType> for char {
967    fn from(t: &ChainType) -> char {
968        match t {
969            ChainType::PrivateChain => '$',
970            ChainType::PublicChain => '#',
971            ChainType::PublicIdentityChain => '@',
972        }
973    }
974}
975
976/// Vector of chain ids.
977pub type ChainsIds = Vec<ChainId>;
978
979/// Freechains chain identifier. Contains its type and name.
980#[derive(Debug, PartialEq, Eq)]
981pub struct ChainId {
982    chain_type: ChainType,
983    name: String,
984}
985
986impl ChainId {
987    /// Creates new [ChainId] from [&str].
988    pub fn new(name: &str) -> Result<ChainId, InvalidChainNameError> {
989        let mut chars = name.chars();
990        let first_char = chars.next();
991        let first_char = first_char.ok_or(InvalidChainNameError)?;
992
993        let chain_type = ChainType::try_from(first_char)?;
994        let name = chars.collect();
995
996        Ok(ChainId { chain_type, name })
997    }
998}
999
1000impl fmt::Display for ChainId {
1001    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1002        write!(f, "{}{}", self.chain_type, self.name)
1003    }
1004}
1005
1006impl std::str::FromStr for ChainId {
1007    type Err = InvalidChainNameError;
1008
1009    fn from_str(s: &str) -> Result<Self, Self::Err> {
1010        ChainId::new(s)
1011    }
1012}
1013
1014/// Representation of freechains content block response. It is created from [content](ChainClient::content).
1015#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1016pub struct ContentBlock {
1017    /// Content hash.
1018    pub hash: String,
1019    /// Content instant of creation. Milliseconds since [std::time::UNIX_EPOCH].
1020    pub time: usize,
1021    /// Content payload information.
1022    #[serde(rename(serialize = "pay", deserialize = "pay"))]
1023    pub payload: Option<PayloadBlock>,
1024    /// If content is a like, like information is stored here.
1025    pub like: Option<LikeBlock>,
1026    /// Content signature information.
1027    #[serde(rename(serialize = "sign", deserialize = "sign"))]
1028    pub signature: Option<SignatureBlock>,
1029    /// Blocks which points to this content block.
1030    pub backs: Vec<String>,
1031}
1032
1033/// Representation of freechains payload field from content block response [ContentBlock].
1034#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1035pub struct PayloadBlock {
1036    /// Payload hash.
1037    pub hash: String,
1038    /// If payload is encrypted.
1039    pub crypt: bool,
1040}
1041
1042/// Representation of freechains like field from content block response [ContentBlock].
1043#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1044pub struct LikeBlock {
1045    /// Like hash.
1046    pub hash: String,
1047    /// Defines if is a like or a dislike. `1` is a like, `-1` is a dislike.
1048    pub n: usize,
1049}
1050
1051/// Representation of freechains signature field from content block response [ContentBlock].
1052#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1053pub struct SignatureBlock {
1054    /// Signature hash.
1055    pub hash: String,
1056    /// Signature public key.
1057    #[serde(rename(serialize = "pub", deserialize = "pub"))]
1058    pub pubkey: String,
1059}
1060
1061/// Error returned when dealing with client-server actions.
1062#[derive(Debug)]
1063pub enum ClientError {
1064    /// Server responded with empty message.
1065    EmptyResponseError,
1066
1067    /// Server responded with execution error. Any server response starting with `!` is reported as
1068    /// an [ExecutionError](ClientError::ExecutionError).
1069    ExecutionError(String),
1070
1071    /// This may only happen if freechains server version is different.
1072    InvalidServerResponseError(String),
1073
1074    /// Error on communication channel.
1075    IoError(io::Error),
1076}
1077
1078impl Error for ClientError {}
1079
1080impl fmt::Display for ClientError {
1081    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1082        match self {
1083            ClientError::EmptyResponseError => write!(f, "empty response"),
1084            ClientError::ExecutionError(e) => write!(f, "execution error: {}", e),
1085            ClientError::InvalidServerResponseError(e) => {
1086                write!(f, "invalid server response: {}", e)
1087            }
1088            ClientError::IoError(e) => e.fmt(f),
1089        }
1090    }
1091}
1092
1093impl From<io::Error> for ClientError {
1094    fn from(error: io::Error) -> Self {
1095        ClientError::IoError(error)
1096    }
1097}
1098
1099impl From<num::ParseIntError> for ClientError {
1100    fn from(error: num::ParseIntError) -> Self {
1101        ClientError::ExecutionError(error.to_string())
1102    }
1103}
1104
1105impl From<serde_json::Error> for ClientError {
1106    fn from(error: serde_json::Error) -> Self {
1107        ClientError::InvalidServerResponseError(error.to_string())
1108    }
1109}
1110
1111impl From<InvalidChainNameError> for ClientError {
1112    fn from(error: InvalidChainNameError) -> Self {
1113        ClientError::InvalidServerResponseError(error.to_string())
1114    }
1115}
1116
1117/// Error returned when a chain is created with invalid name.
1118#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1119pub struct InvalidChainNameError;
1120
1121impl Error for InvalidChainNameError {}
1122
1123impl fmt::Display for InvalidChainNameError {
1124    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1125        write!(f, "chain name must start with '$', '@' or '#'")
1126    }
1127}
1128
1129fn read_utf8_line(mut stream: impl BufRead) -> io::Result<String> {
1130    let mut line = String::new();
1131    let size = stream.read_line(&mut line)?;
1132    if size > 0 && line.chars().last().expect("must have last character") == '\n' {
1133        line = String::from(&line[..size - 1]);
1134    }
1135
1136    Ok(line)
1137}
1138
1139fn parse_server_message(msg: &str) -> Result<String, ClientError> {
1140    if msg.len() == 0 {
1141        return Err(ClientError::EmptyResponseError);
1142    }
1143
1144    let mut chars = msg.chars();
1145
1146    let first_char = chars.next();
1147    if let Some(c) = first_char {
1148        if c == '!' {
1149            chars.next();
1150            return Err(ClientError::ExecutionError(chars.collect()));
1151        }
1152    }
1153
1154    Ok(String::from(msg))
1155}
1156
1157#[cfg(test)]
1158mod test {
1159    use std::cell::RefCell;
1160    use std::rc::Rc;
1161    use std::time::Duration;
1162
1163    use super::*;
1164
1165    #[derive(Debug)]
1166    struct ConnectorMock(ConnectionMock);
1167
1168    impl ConnectorMock {
1169        fn new() -> ConnectorMock {
1170            ConnectorMock(ConnectionMock::new())
1171        }
1172
1173        fn read_stream(&self) -> Rc<RefCell<Vec<u8>>> {
1174            self.0.read_stream()
1175        }
1176    }
1177
1178    impl Connect for ConnectorMock {
1179        fn connect(&self) -> io::Result<Box<dyn ReadWrite>> {
1180            Ok(Box::new(self.0.clone()))
1181        }
1182    }
1183
1184    #[derive(Debug)]
1185    struct ConnectionMock {
1186        read_stream: Rc<RefCell<Vec<u8>>>,
1187        write_stream: Rc<RefCell<Vec<u8>>>,
1188    }
1189
1190    impl ConnectionMock {
1191        fn new() -> ConnectionMock {
1192            let read_stream = Rc::new(RefCell::new(Vec::new()));
1193            let write_stream = Rc::new(RefCell::new(Vec::new()));
1194
1195            ConnectionMock {
1196                read_stream,
1197                write_stream,
1198            }
1199        }
1200
1201        fn read_stream(&self) -> Rc<RefCell<Vec<u8>>> {
1202            Rc::clone(&self.read_stream)
1203        }
1204    }
1205
1206    impl ReadWrite for ConnectionMock {}
1207
1208    impl Read for ConnectionMock {
1209        fn read(&mut self, b: &mut [u8]) -> io::Result<usize> {
1210            let mut read_stream = self.read_stream.borrow_mut();
1211            let mut a: &[u8] = read_stream.as_ref();
1212
1213            let size = a.read(b)?;
1214            read_stream.drain(..size);
1215            Ok(size)
1216        }
1217    }
1218
1219    impl Write for ConnectionMock {
1220        fn write(&mut self, b: &[u8]) -> io::Result<usize> {
1221            let mut a = self.write_stream.borrow_mut();
1222            a.write(b)
1223        }
1224
1225        fn flush(&mut self) -> io::Result<()> {
1226            Ok(())
1227        }
1228    }
1229
1230    impl Clone for ConnectionMock {
1231        fn clone(&self) -> Self {
1232            let read_stream = Rc::clone(&self.read_stream);
1233            let write_stream = Rc::clone(&self.write_stream);
1234
1235            ConnectionMock {
1236                read_stream,
1237                write_stream,
1238            }
1239        }
1240    }
1241
1242    #[derive(Debug)]
1243    struct SyncConnectorMock(Vec<u8>);
1244
1245    impl SyncConnectorMock {
1246        fn new(r: &[u8]) -> SyncConnectorMock {
1247            SyncConnectorMock(r.to_vec())
1248        }
1249    }
1250
1251    impl Clone for SyncConnectorMock {
1252        fn clone(&self) -> Self {
1253            SyncConnectorMock(self.0.clone())
1254        }
1255    }
1256
1257    impl Connect for SyncConnectorMock {
1258        fn connect(&self) -> io::Result<Box<dyn ReadWrite>> {
1259            Ok(Box::new(SyncConnectionMock::new(&self.0)))
1260        }
1261    }
1262
1263    #[derive(Debug)]
1264    struct SyncConnectionMock {
1265        response: Vec<u8>,
1266        sent: Vec<u8>,
1267    }
1268
1269    impl SyncConnectionMock {
1270        fn new(response: &[u8]) -> SyncConnectionMock {
1271            let response = response.to_vec();
1272            let sent = Vec::new();
1273            SyncConnectionMock { response, sent }
1274        }
1275    }
1276
1277    impl ReadWrite for SyncConnectionMock {}
1278
1279    impl Read for SyncConnectionMock {
1280        fn read(&mut self, b: &mut [u8]) -> io::Result<usize> {
1281            let mut a: &[u8] = &self.response;
1282
1283            let size = a.read(b)?;
1284            self.response.drain(..size);
1285            Ok(size)
1286        }
1287    }
1288
1289    impl Write for SyncConnectionMock {
1290        fn write(&mut self, b: &[u8]) -> io::Result<usize> {
1291            self.sent.write(b)
1292        }
1293
1294        fn flush(&mut self) -> io::Result<()> {
1295            Ok(())
1296        }
1297    }
1298
1299    #[test]
1300    fn crypto_shared() -> Result<(), Box<dyn Error>> {
1301        let mock = ConnectorMock::new();
1302        let w = mock.read_stream();
1303        let client = Client::new(mock);
1304
1305        let response = b"707E8334560C3FB2852CCCE11F9221FA3594B7DE3919A8BAA5B6DB90FE432E53\n";
1306        w.replace(response.to_vec());
1307        let hash = client.crypto_shared("pwd")?;
1308
1309        assert_eq!(hash.as_bytes(), &response[..response.len() - 1]);
1310
1311        Ok(())
1312    }
1313
1314    #[test]
1315    fn crypto_pubpvt() -> Result<(), Box<dyn Error>> {
1316        let mock = ConnectorMock::new();
1317        let w = mock.read_stream();
1318        let client = Client::new(mock);
1319
1320        let response = b"646AABC0D78E87574FFCD2E98FF14B08C76D424C7A0ED783CB7B840117A403E3 \
1321                       707E8334560C3FB2852CCCE11F9221FA3594B7DE3919A8BAA5B6DB90FE432E53646AABC0D78E87574FFCD2E98FF14B08C76D424C7A0ED783CB7B840117A403E3\n";
1322        w.replace(response.to_vec());
1323        let (pubkey, pvtkey) = client.crypto_pubpvt("pwd")?;
1324
1325        let exp_pubkey = "646AABC0D78E87574FFCD2E98FF14B08C76D424C7A0ED783CB7B840117A403E3";
1326        assert_eq!(pubkey, exp_pubkey);
1327
1328        let exp_pvtkey = "707E8334560C3FB2852CCCE11F9221FA3594B7DE3919A8BAA5B6DB90FE432E53646AABC0D78E87574FFCD2E98FF14B08C76D424C7A0ED783CB7B840117A403E3";
1329        assert_eq!(pvtkey, exp_pvtkey);
1330
1331        Ok(())
1332    }
1333
1334    #[test]
1335    fn listen() -> Result<(), Box<dyn Error>> {
1336        let response = b"5 #forum\n10 #chat\n2 $family\n";
1337        let mock = SyncConnectorMock::new(response);
1338        let client = Client::new(mock);
1339
1340        let listener = client.listen();
1341
1342        let (updates, chain) = listener.recv_timeout(Duration::new(1, 0))??;
1343        assert_eq!(updates, 5);
1344        assert_eq!(chain, ChainId::new("#forum").unwrap());
1345
1346        let (updates, chain) = listener.recv_timeout(Duration::new(1, 0))??;
1347        assert_eq!(updates, 10);
1348        assert_eq!(chain, ChainId::new("#chat").unwrap());
1349
1350        let (updates, chain) = listener.recv_timeout(Duration::new(1, 0))??;
1351        assert_eq!(updates, 2);
1352        assert_eq!(chain, ChainId::new("$family").unwrap());
1353
1354        Ok(())
1355    }
1356
1357    #[test]
1358    fn chains_join() -> Result<(), Box<dyn Error>> {
1359        let mock = ConnectorMock::new();
1360        let w = mock.read_stream();
1361        let client = Client::new(mock);
1362
1363        let chain_id = ChainId::new("$chat")?;
1364        let pwd_hash = "707E8334560C3FB2852CCCE11F9221FA3594B7DE3919A8BAA5B6DB90FE432E53";
1365
1366        let response = b"3E56AE4D17484398F7694C75EA6D3F9FD31C756574B4346D0BA40FB36DAB4501\n";
1367        w.replace(response.to_vec());
1368        let hash = client.join_chain(&chain_id, &[pwd_hash])?;
1369
1370        let exp_hash = &response[..response.len() - 1];
1371        assert_eq!(hash.as_bytes(), exp_hash);
1372
1373        Ok(())
1374    }
1375
1376    #[test]
1377    fn chains_list() -> Result<(), Box<dyn Error>> {
1378        let mock = ConnectorMock::new();
1379        let w = mock.read_stream();
1380        let client = Client::new(mock);
1381
1382        let response = b"$chain1 $chain2 #chain3 @chain4\n";
1383        w.replace(response.to_vec());
1384        let chains = client.chains()?;
1385
1386        let exp_chains: Result<Vec<_>, _> = std::str::from_utf8(&response[..response.len() - 1])
1387            .unwrap()
1388            .split(' ')
1389            .map(ChainId::new)
1390            .collect();
1391        let exp_chains = exp_chains?;
1392        assert_eq!(chains, exp_chains);
1393
1394        Ok(())
1395    }
1396
1397    #[test]
1398    fn chain_leave() -> Result<(), Box<dyn Error>> {
1399        let mock = ConnectorMock::new();
1400        let w = mock.read_stream();
1401        let client = Client::new(mock);
1402
1403        let chain_id = ChainId::new("$chat").unwrap();
1404
1405        let response = b"true\n";
1406        w.replace(response.to_vec());
1407        let left = client.chain(&chain_id).leave()?;
1408
1409        assert_eq!(left, true);
1410
1411        Ok(())
1412    }
1413
1414    #[test]
1415    fn chain_leave_failed() -> Result<(), Box<dyn Error>> {
1416        let mock = ConnectorMock::new();
1417        let w = mock.read_stream();
1418        let client = Client::new(mock);
1419
1420        let chain_id = ChainId::new("$chat").unwrap();
1421
1422        let response = b"false\n";
1423        w.replace(response.to_vec());
1424        let left = client.chain(&chain_id).leave()?;
1425
1426        assert_eq!(left, false);
1427
1428        Ok(())
1429    }
1430
1431    #[test]
1432    fn chain_genesis() -> Result<(), Box<dyn Error>> {
1433        let mock = ConnectorMock::new();
1434        let w = mock.read_stream();
1435        let client = Client::new(mock);
1436
1437        let chain_id = ChainId::new("$chat").unwrap();
1438
1439        let response = b"0_3E56AE4D17484398F7694C75EA6D3F9FD31C756574B4346D0BA40FB36DAB4501\n";
1440        w.replace(response.to_vec());
1441        let genesis = client.chain(&chain_id).genesis()?;
1442
1443        let exp_genesis = &response[..response.len() - 1];
1444        assert_eq!(genesis.as_bytes(), exp_genesis);
1445
1446        Ok(())
1447    }
1448
1449    #[test]
1450    fn chain_heads() -> Result<(), Box<dyn Error>> {
1451        let mock = ConnectorMock::new();
1452        let w = mock.read_stream();
1453        let client = Client::new(mock);
1454
1455        let chain_id = ChainId::new("$chat").unwrap();
1456
1457        let response = b"1_B8AAB63B4CC2129443F0BEA3F1A7FB16C193FE92C3DB2245EB9062EB07A47159 \
1458                       2_30F9ABD1FDB2DF44CAF47743AE01FD768B1C2B1952B74A761F32E13D5483BE0E\n";
1459        w.replace(response.to_vec());
1460        let heads = client.chain(&chain_id).heads(false)?;
1461
1462        let exp_heads: Vec<_> = std::str::from_utf8(&response[..response.len() - 1])
1463            .unwrap()
1464            .split(' ')
1465            .map(String::from)
1466            .collect();
1467        assert_eq!(heads, exp_heads);
1468
1469        Ok(())
1470    }
1471
1472    #[test]
1473    fn chain_payload() -> Result<(), Box<dyn Error>> {
1474        let mock = ConnectorMock::new();
1475        let w = mock.read_stream();
1476        let client = Client::new(mock);
1477
1478        let chain_id = ChainId::new("$chat").unwrap();
1479
1480        let response = b"17\nhello,\nok message";
1481        w.replace(response.to_vec());
1482        let payload = client.chain(&chain_id).payload("some_hash", None)?;
1483        let payload = std::str::from_utf8(&payload)?;
1484
1485        let exp_payload = std::str::from_utf8(response)
1486            .unwrap()
1487            .split_once('\n')
1488            .unwrap()
1489            .1;
1490        assert_eq!(payload, exp_payload);
1491
1492        Ok(())
1493    }
1494
1495    #[test]
1496    fn chain_content_block() -> Result<(), Box<dyn Error>> {
1497        let mock = ConnectorMock::new();
1498        let w = mock.read_stream();
1499        let client = Client::new(mock);
1500
1501        let chain_id = ChainId::new("$chat").unwrap();
1502        let response = br#"710
1503{
1504    "hash": "2_3D9997F5D8A57B26B7DEC3BCDFD3A12EAE48C585DFC287FE9DE522E44A96DB46",
1505    "time": 1635174995341,
1506    "pay": {
1507        "crypt": false,
1508        "hash": "FD7B3E075AB7E245506323F1D1B55330CCD606FB3301F4CABA25022A1DE50B47"
1509    },
1510    "like": {
1511        "n": 1,
1512        "hash": "1_59C6AA0E3B3650D44D27EB8EF3645ECFC69A1B78F0DAA74A2B89167538749103"
1513    },
1514    "sign": {
1515        "hash": "9EB2D4CB0AC67B35B78B545E4AB3410DC82E6D0778DECFC80C83A13BEAD05F7B83D5B8B6CE67AB7EF9E0CA09FA489AE88FB0D126C522A0ED4E2C5BF939901205",
1516        "pub": "197154707DAF7953BE0EBB7BBE29FA1AECA402505E9ED00BCAF189EB6A32FCE8"
1517    },
1518    "backs": [
1519        "1_59C6AA0E3B3650D44D27EB8EF3645ECFC69A1B78F0DAA74A2B89167538749103"
1520    ]
1521}"#;
1522
1523        w.replace(response.to_vec());
1524        let content = client.chain(&chain_id).content("some_hash", None)?;
1525
1526        let exp_content = ContentBlock {
1527            hash: "2_3D9997F5D8A57B26B7DEC3BCDFD3A12EAE48C585DFC287FE9DE522E44A96DB46".to_string(),
1528            time: 1635174995341,
1529            payload: Some(PayloadBlock {
1530                crypt: false,
1531                hash: "FD7B3E075AB7E245506323F1D1B55330CCD606FB3301F4CABA25022A1DE50B47"
1532                    .to_string(),
1533            }),
1534            like: Some(LikeBlock {
1535                n: 1,
1536                hash: "1_59C6AA0E3B3650D44D27EB8EF3645ECFC69A1B78F0DAA74A2B89167538749103"
1537                    .to_string(),
1538            }),
1539            signature: Some(SignatureBlock {
1540                hash:"9EB2D4CB0AC67B35B78B545E4AB3410DC82E6D0778DECFC80C83A13BEAD05F7B83D5B8B6CE67AB7EF9E0CA09FA489AE88FB0D126C522A0ED4E2C5BF939901205".to_string(),
1541        pubkey: "197154707DAF7953BE0EBB7BBE29FA1AECA402505E9ED00BCAF189EB6A32FCE8".to_string(),
1542            }),
1543            backs: vec![
1544        "1_59C6AA0E3B3650D44D27EB8EF3645ECFC69A1B78F0DAA74A2B89167538749103".to_string()],
1545        };
1546        assert_eq!(content, exp_content);
1547
1548        Ok(())
1549    }
1550
1551    #[test]
1552    fn chain_post() -> Result<(), Box<dyn Error>> {
1553        let mock = ConnectorMock::new();
1554        let w = mock.read_stream();
1555        let client = Client::new(mock);
1556
1557        let chain_id = ChainId::new("$chat").unwrap();
1558
1559        let response = b"3_9B144EE0518E5DE01D4C1AABD469D85315715CB15F77AB4B3D87D7802EE970E6\n";
1560        w.replace(response.to_vec());
1561        let hash = client.chain(&chain_id).post(None, false, b"payload")?;
1562
1563        let exp_hash = std::str::from_utf8(&response[..response.len() - 1]).unwrap();
1564        assert_eq!(hash, exp_hash);
1565
1566        Ok(())
1567    }
1568
1569    #[test]
1570    fn chain_like() -> Result<(), Box<dyn Error>> {
1571        let mock = ConnectorMock::new();
1572        let w = mock.read_stream();
1573        let client = Client::new(mock);
1574
1575        let chain_id = ChainId::new("$chat").unwrap();
1576
1577        let response = b"2_3D9997F5D8A57B26B7DEC3BCDFD3A12EAE48C585DFC287FE9DE522E44A96DB46\n";
1578        w.replace(response.to_vec());
1579
1580        let fake_hash = "1_9B144EE0518E5DE01D4C1AABD469D85315715CB15F77AB4B3D87D7802EE970E6";
1581        let hash = client
1582            .chain(&chain_id)
1583            .like(&fake_hash, "pvt_key", b"i liked it")?;
1584
1585        let exp_hash = std::str::from_utf8(&response[..response.len() - 1]).unwrap();
1586        assert_eq!(hash, exp_hash);
1587
1588        Ok(())
1589    }
1590
1591    #[test]
1592    fn chain_dislike() -> Result<(), Box<dyn Error>> {
1593        let mock = ConnectorMock::new();
1594        let w = mock.read_stream();
1595        let client = Client::new(mock);
1596
1597        let chain_id = ChainId::new("$chat").unwrap();
1598
1599        let response = b"2_3D9997F5D8A57B26B7DEC3BCDFD3A12EAE48C585DFC287FE9DE522E44A96DB46\n";
1600        w.replace(response.to_vec());
1601
1602        let fake_hash = "1_9B144EE0518E5DE01D4C1AABD469D85315715CB15F77AB4B3D87D7802EE970E6";
1603        let hash = client
1604            .chain(&chain_id)
1605            .dislike(&fake_hash, "pvt_key", b"i liked it")?;
1606
1607        let exp_hash = std::str::from_utf8(&response[..response.len() - 1]).unwrap();
1608        assert_eq!(hash, exp_hash);
1609
1610        Ok(())
1611    }
1612
1613    #[test]
1614    fn chain_reputation() -> Result<(), Box<dyn Error>> {
1615        let mock = ConnectorMock::new();
1616        let w = mock.read_stream();
1617        let client = Client::new(mock);
1618
1619        let chain_id = ChainId::new("$chat").unwrap();
1620
1621        let response = b"18\n";
1622        w.replace(response.to_vec());
1623
1624        let fake_hash = "1_9B144EE0518E5DE01D4C1AABD469D85315715CB15F77AB4B3D87D7802EE970E6";
1625        let reps = client.chain(&chain_id).reputation(&fake_hash)?;
1626
1627        let exp_reps = 18;
1628        assert_eq!(reps, exp_reps);
1629
1630        Ok(())
1631    }
1632
1633    #[test]
1634    fn chain_consensus() -> Result<(), Box<dyn Error>> {
1635        let mock = ConnectorMock::new();
1636        let w = mock.read_stream();
1637        let client = Client::new(mock);
1638
1639        let chain_id = ChainId::new("$chat").unwrap();
1640
1641        let response = b"1_B8AAB63B4CC2129443F0BEA3F1A7FB16C193FE92C3DB2245EB9062EB07A47159 \
1642                       2_30F9ABD1FDB2DF44CAF47743AE01FD768B1C2B1952B74A761F32E13D5483BE0E \
1643                       3_3D9997F5D8A57B26B7DEC3BCDFD3A12EAE48C585DFC287FE9DE522E44A96DB46\n";
1644        w.replace(response.to_vec());
1645
1646        let hashes = client.chain(&chain_id).consensus()?;
1647
1648        let exp_hahses: Vec<_> = std::str::from_utf8(&response[..response.len() - 1])
1649            .unwrap()
1650            .split(' ')
1651            .map(String::from)
1652            .collect();
1653        assert_eq!(hashes, exp_hahses);
1654
1655        Ok(())
1656    }
1657
1658    #[test]
1659    fn chain_listen() -> Result<(), Box<dyn Error>> {
1660        let response = b"9\n6\n7\n9\n";
1661        let mock = SyncConnectorMock::new(response);
1662        let client = Client::new(mock);
1663
1664        let chain_id = ChainId::new("#forum").unwrap();
1665
1666        let listener = client.chain(&chain_id).listen();
1667
1668        let updates = listener.recv_timeout(Duration::new(1, 0))??;
1669        assert_eq!(updates, 9);
1670
1671        let updates = listener.recv_timeout(Duration::new(1, 0))??;
1672        assert_eq!(updates, 6);
1673
1674        let updates = listener.recv_timeout(Duration::new(1, 0))??;
1675        assert_eq!(updates, 7);
1676
1677        let updates = listener.recv_timeout(Duration::new(1, 0))??;
1678        assert_eq!(updates, 9);
1679
1680        Ok(())
1681    }
1682
1683    #[test]
1684    fn peer_ping() -> Result<(), Box<dyn Error>> {
1685        let mock = ConnectorMock::new();
1686        let w = mock.read_stream();
1687        let client = Client::new(mock);
1688
1689        let fake_peer = "1.2.3.4:8330";
1690
1691        let response = b"10\n";
1692        w.replace(response.to_vec());
1693
1694        let ping = client.peer(&fake_peer)?.ping()?;
1695
1696        let exp_ping = 10;
1697        assert_eq!(ping, exp_ping);
1698
1699        Ok(())
1700    }
1701
1702    #[test]
1703    fn peer_chains_list() -> Result<(), Box<dyn Error>> {
1704        let mock = ConnectorMock::new();
1705        let w = mock.read_stream();
1706        let client = Client::new(mock);
1707
1708        let fake_peer = "1.2.3.4:8330";
1709
1710        let response = b"$chain1 $chain2 #chain3 @chain4\n";
1711        w.replace(response.to_vec());
1712        let chains = client.peer(&fake_peer)?.chains()?;
1713
1714        let exp_chains: Result<Vec<_>, _> = std::str::from_utf8(&response[..response.len() - 1])
1715            .unwrap()
1716            .split(' ')
1717            .map(ChainId::new)
1718            .collect();
1719        let exp_chains = exp_chains?;
1720        assert_eq!(chains, exp_chains);
1721
1722        Ok(())
1723    }
1724
1725    #[test]
1726    fn peer_send() -> Result<(), Box<dyn Error>> {
1727        let mock = ConnectorMock::new();
1728        let w = mock.read_stream();
1729        let client = Client::new(mock);
1730
1731        let fake_peer = "1.2.3.4:8330";
1732        let chain_id = ChainId::new("#forum").unwrap();
1733
1734        let response = b"5 / 8\n";
1735        w.replace(response.to_vec());
1736
1737        let sent = client.peer(&fake_peer)?.send_chain(&chain_id)?;
1738
1739        let exp_sent = (5, 8);
1740        assert_eq!(sent, exp_sent);
1741
1742        Ok(())
1743    }
1744
1745    #[test]
1746    fn peer_receive() -> Result<(), Box<dyn Error>> {
1747        let mock = ConnectorMock::new();
1748        let w = mock.read_stream();
1749        let client = Client::new(mock);
1750
1751        let fake_peer = "1.2.3.4:8330";
1752        let chain_id = ChainId::new("#forum").unwrap();
1753
1754        let response = b"5 / 8\n";
1755        w.replace(response.to_vec());
1756
1757        let received = client.peer(&fake_peer)?.receive_chain(&chain_id)?;
1758
1759        let exp_received = (5, 8);
1760        assert_eq!(received, exp_received);
1761
1762        Ok(())
1763    }
1764
1765    #[test]
1766    fn host_get_time() -> Result<(), Box<dyn Error>> {
1767        let mock = ConnectorMock::new();
1768        let w = mock.read_stream();
1769        let client = Client::new(mock);
1770
1771        let response = b"1635310954\n";
1772        w.replace(response.to_vec());
1773
1774        let time = client.host().time()?;
1775
1776        let exp_time = 1635310954;
1777        assert_eq!(time, exp_time);
1778
1779        Ok(())
1780    }
1781
1782    #[test]
1783    fn host_set_time() -> Result<(), Box<dyn Error>> {
1784        let mock = ConnectorMock::new();
1785        let w = mock.read_stream();
1786        let client = Client::new(mock);
1787
1788        let response = b"1635310954\n";
1789        w.replace(response.to_vec());
1790
1791        let time = client.host().set_time(1635310954)?;
1792
1793        let exp_time = 1635310954;
1794        assert_eq!(time, exp_time);
1795
1796        Ok(())
1797    }
1798
1799    #[test]
1800    fn host_path() -> Result<(), Box<dyn Error>> {
1801        let mock = ConnectorMock::new();
1802        let w = mock.read_stream();
1803        let client = Client::new(mock);
1804
1805        let response = b"/tmp/freechains\n";
1806        w.replace(response.to_vec());
1807
1808        let path = client.host().path()?;
1809
1810        let exp_path = std::str::from_utf8(&response[..response.len() - 1]).unwrap();
1811        assert_eq!(path, exp_path);
1812
1813        Ok(())
1814    }
1815
1816    #[test]
1817    fn host_stop() -> Result<(), Box<dyn Error>> {
1818        let mock = ConnectorMock::new();
1819        let w = mock.read_stream();
1820        let client = Client::new(mock);
1821
1822        let response = b"true\n";
1823        w.replace(response.to_vec());
1824
1825        let stopped = client.host().stop()?;
1826
1827        let exp_stopped = true;
1828        assert_eq!(stopped, exp_stopped);
1829
1830        Ok(())
1831    }
1832}