libp2p_pubsub_core/
topic.rs

1use std::convert::Infallible;
2use std::fmt;
3use std::str::FromStr;
4
5use base64::prelude::*;
6use prost::Message as _;
7use sha2::{Digest, Sha256};
8
9use libp2p_pubsub_proto::topic_descriptor::TopicDescriptorProto;
10
11pub type IdentTopic = Topic<IdentityHash>;
12pub type Sha256Topic = Topic<Sha256Hash>;
13
14/// A generic trait that can be extended for various hashing frame for a topic.
15pub trait Hasher {
16    /// The function that takes a topic string and creates a topic hash.
17    fn hash(topic: String) -> TopicHash;
18}
19
20/// A type for representing topics who use the identity hash.
21#[derive(Debug, Clone)]
22pub struct IdentityHash;
23
24impl Hasher for IdentityHash {
25    /// Creates a [`TopicHash`] as a raw string.
26    fn hash(topic_string: String) -> TopicHash {
27        TopicHash { hash: topic_string }
28    }
29}
30
31#[derive(Debug, Clone)]
32pub struct Sha256Hash;
33
34impl Hasher for Sha256Hash {
35    /// Creates a [`TopicHash`] by SHA256 hashing the topic then base64 encoding the
36    /// hash.
37    fn hash(topic_string: String) -> TopicHash {
38        let topic_descriptor = TopicDescriptorProto {
39            name: Some(topic_string),
40            auth: None,
41            enc: None,
42        };
43        let mut bytes = Vec::with_capacity(topic_descriptor.encoded_len());
44        topic_descriptor
45            .encode(&mut bytes)
46            .expect("Encoding to succeed");
47        let hash = BASE64_STANDARD.encode(Sha256::digest(&bytes));
48        TopicHash { hash }
49    }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
53pub struct TopicHash {
54    /// The topic hash. Stored as a string to align with the protobuf API.
55    hash: String,
56}
57
58impl TopicHash {
59    pub fn from_raw<T: Into<String>>(raw: T) -> Self {
60        Self { hash: raw.into() }
61    }
62
63    pub fn into_string(self) -> String {
64        self.hash
65    }
66
67    pub fn as_str(&self) -> &str {
68        &self.hash
69    }
70}
71
72impl<T: Into<String>> From<T> for TopicHash {
73    fn from(hash: T) -> Self {
74        Self::from_raw(hash)
75    }
76}
77
78impl FromStr for TopicHash {
79    type Err = Infallible;
80
81    fn from_str(s: &str) -> Result<Self, Self::Err> {
82        Ok(Self::from_raw(s))
83    }
84}
85
86impl AsRef<str> for TopicHash {
87    fn as_ref(&self) -> &str {
88        self.as_str()
89    }
90}
91
92/// A pub-sub topic.
93#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
94pub struct Topic<H: Hasher> {
95    topic: String,
96    phantom_data: std::marker::PhantomData<H>,
97}
98
99impl<H: Hasher> From<Topic<H>> for TopicHash {
100    fn from(topic: Topic<H>) -> TopicHash {
101        topic.hash()
102    }
103}
104
105impl<H: Hasher> Topic<H> {
106    pub fn new<T: Into<String>>(topic: T) -> Self {
107        Topic {
108            topic: topic.into(),
109            phantom_data: std::marker::PhantomData,
110        }
111    }
112
113    pub fn hash(&self) -> TopicHash {
114        H::hash(self.topic.clone())
115    }
116}
117
118impl<H: Hasher> fmt::Display for Topic<H> {
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        write!(f, "{}", self.topic)
121    }
122}
123
124impl fmt::Display for TopicHash {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        write!(f, "{}", self.hash)
127    }
128}