libp2p_gossipsub/
topic.rs

1// Copyright 2020 Sigma Prime Pty Ltd.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21use std::fmt;
22
23use base64::prelude::*;
24use prometheus_client::encoding::EncodeLabelSet;
25use quick_protobuf::Writer;
26use sha2::{Digest, Sha256};
27
28use crate::rpc_proto::proto;
29
30/// A generic trait that can be extended for various hashing types for a topic.
31pub trait Hasher {
32    /// The function that takes a topic string and creates a topic hash.
33    fn hash(topic_string: String) -> TopicHash;
34}
35
36/// A type for representing topics who use the identity hash.
37#[derive(Debug, Clone)]
38pub struct IdentityHash {}
39impl Hasher for IdentityHash {
40    /// Creates a [`TopicHash`] as a raw string.
41    fn hash(topic_string: String) -> TopicHash {
42        TopicHash { hash: topic_string }
43    }
44}
45
46#[derive(Debug, Clone)]
47pub struct Sha256Hash {}
48impl Hasher for Sha256Hash {
49    /// Creates a [`TopicHash`] by SHA256 hashing the topic then base64 encoding the
50    /// hash.
51    fn hash(topic_string: String) -> TopicHash {
52        use quick_protobuf::MessageWrite;
53
54        let topic_descripter = proto::TopicDescriptor {
55            name: Some(topic_string),
56            auth: None,
57            enc: None,
58        };
59        let mut bytes = Vec::with_capacity(topic_descripter.get_size());
60        let mut writer = Writer::new(&mut bytes);
61        topic_descripter
62            .write_message(&mut writer)
63            .expect("Encoding to succeed");
64        let hash = BASE64_STANDARD.encode(Sha256::digest(&bytes));
65        TopicHash { hash }
66    }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, EncodeLabelSet)]
70pub struct TopicHash {
71    /// The topic hash. Stored as a string to align with the protobuf API.
72    hash: String,
73}
74
75impl TopicHash {
76    pub fn from_raw(hash: impl Into<String>) -> TopicHash {
77        TopicHash { hash: hash.into() }
78    }
79
80    pub fn into_string(self) -> String {
81        self.hash
82    }
83
84    pub fn as_str(&self) -> &str {
85        &self.hash
86    }
87}
88
89/// A gossipsub topic.
90#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
91pub struct Topic<H: Hasher> {
92    topic: String,
93    phantom_data: std::marker::PhantomData<H>,
94}
95
96impl<H: Hasher> From<Topic<H>> for TopicHash {
97    fn from(topic: Topic<H>) -> TopicHash {
98        topic.hash()
99    }
100}
101
102impl<H: Hasher> Topic<H> {
103    pub fn new(topic: impl Into<String>) -> Self {
104        Topic {
105            topic: topic.into(),
106            phantom_data: std::marker::PhantomData,
107        }
108    }
109
110    pub fn hash(&self) -> TopicHash {
111        H::hash(self.topic.clone())
112    }
113}
114
115impl<H: Hasher> fmt::Display for Topic<H> {
116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117        write!(f, "{}", self.topic)
118    }
119}
120
121impl fmt::Display for TopicHash {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        write!(f, "{}", self.hash)
124    }
125}