aboutsummaryrefslogtreecommitdiffstats
path: root/whisper/topic.go
blob: b2a264e299c9c7ce0405bd83e891c4ff991424cc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Contains the Whisper protocol Topic element. For formal details please see
// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#topics.

package whisper

import "github.com/ethereum/go-ethereum/crypto"

// Topic represents a cryptographically secure, probabilistic partial
// classifications of a message, determined as the first (left) 4 bytes of the
// SHA3 hash of some arbitrary data given by the original author of the message.
type Topic [4]byte

// NewTopic creates a topic from the 4 byte prefix of the SHA3 hash of the data.
func NewTopic(data []byte) Topic {
    prefix := [4]byte{}
    copy(prefix[:], crypto.Sha3(data)[:4])
    return Topic(prefix)
}

// NewTopics creates a list of topics from a list of binary data elements, by
// iteratively calling NewTopic on each of them.
func NewTopics(data ...[]byte) []Topic {
    topics := make([]Topic, len(data))
    for i, element := range data {
        topics[i] = NewTopic(element)
    }
    return topics
}

// NewTopicFilter creates a 2D topic array used by whisper.Filter from binary
// data elements.
func NewTopicFilter(data ...[][]byte) [][]Topic {
    filter := make([][]Topic, len(data))
    for i, condition := range data {
        filter[i] = NewTopics(condition...)
    }
    return filter
}

// NewTopicFilterFlat creates a 2D topic array used by whisper.Filter from flat
// binary data elements.
func NewTopicFilterFlat(data ...[]byte) [][]Topic {
    filter := make([][]Topic, len(data))
    for i, element := range data {
        filter[i] = []Topic{NewTopic(element)}
    }
    return filter
}

// NewTopicFromString creates a topic using the binary data contents of the
// specified string.
func NewTopicFromString(data string) Topic {
    return NewTopic([]byte(data))
}

// NewTopicsFromStrings creates a list of topics from a list of textual data
// elements, by iteratively calling NewTopicFromString on each of them.
func NewTopicsFromStrings(data ...string) []Topic {
    topics := make([]Topic, len(data))
    for i, element := range data {
        topics[i] = NewTopicFromString(element)
    }
    return topics
}

// NewTopicFilterFromStrings creates a 2D topic array used by whisper.Filter
// from textual data elements.
func NewTopicFilterFromStrings(data ...[]string) [][]Topic {
    filter := make([][]Topic, len(data))
    for i, condition := range data {
        filter[i] = NewTopicsFromStrings(condition...)
    }
    return filter
}

// NewTopicFilterFromStringsFlat creates a 2D topic array used by whisper.Filter from flat
// binary data elements.
func NewTopicFilterFromStringsFlat(data ...string) [][]Topic {
    filter := make([][]Topic, len(data))
    for i, element := range data {
        filter[i] = []Topic{NewTopicFromString(element)}
    }
    return filter
}

// String converts a topic byte array to a string representation.
func (self *Topic) String() string {
    return string(self[:])
}

// topicMatcher is a filter expression to verify if a list of topics contained
// in an arriving message matches some topic conditions. The topic matcher is
// built up of a list of conditions, each of which must be satisfied by the
// corresponding topic in the message. Each condition may require: a) an exact
// topic match; b) a match from a set of topics; or c) a wild-card matching all.
//
// If a message contains more topics than required by the matcher, those beyond
// the condition count are ignored and assumed to match.
//
// Consider the following sample topic matcher:
//   sample := {
//     {TopicA1, TopicA2, TopicA3},
//     {TopicB},
//     nil,
//     {TopicD1, TopicD2}
//   }
// In order for a message to pass this filter, it should enumerate at least 4
// topics, the first any of [TopicA1, TopicA2, TopicA3], the second mandatory
// "TopicB", the third is ignored by the filter and the fourth either "TopicD1"
// or "TopicD2". If the message contains further topics, the filter will match
// them too.
type topicMatcher struct {
    conditions []map[Topic]struct{}
}

// newTopicMatcher create a topic matcher from a list of topic conditions.
func newTopicMatcher(topics ...[]Topic) *topicMatcher {
    matcher := make([]map[Topic]struct{}, len(topics))
    for i, condition := range topics {
        matcher[i] = make(map[Topic]struct{})
        for _, topic := range condition {
            matcher[i][topic] = struct{}{}
        }
    }
    return &topicMatcher{conditions: matcher}
}

// newTopicMatcherFromBinary create a topic matcher from a list of binary conditions.
func newTopicMatcherFromBinary(data ...[][]byte) *topicMatcher {
    topics := make([][]Topic, len(data))
    for i, condition := range data {
        topics[i] = NewTopics(condition...)
    }
    return newTopicMatcher(topics...)
}

// newTopicMatcherFromStrings creates a topic matcher from a list of textual
// conditions.
func newTopicMatcherFromStrings(data ...[]string) *topicMatcher {
    topics := make([][]Topic, len(data))
    for i, condition := range data {
        topics[i] = NewTopicsFromStrings(condition...)
    }
    return newTopicMatcher(topics...)
}

// Matches checks if a list of topics matches this particular condition set.
func (self *topicMatcher) Matches(topics []Topic) bool {
    // Mismatch if there aren't enough topics
    if len(self.conditions) > len(topics) {
        return false
    }
    // Check each topic condition for existence (skip wild-cards)
    for i := 0; i < len(topics) && i < len(self.conditions); i++ {
        if len(self.conditions[i]) > 0 {
            if _, ok := self.conditions[i][topics[i]]; !ok {
                return false
            }
        }
    }
    return true
}