Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1''' 

2Relay Filter List 

3================= 

4 

5Parse a relay filter list file and decide whether or not we should measure a 

6relay based on its fingerprint. 

7 

8Files are line and word based and read left-to-right, top-to-bottom. First word 

9to match a given fingerprint wins. 

10 

11Syntax 

12------ 

13 

14Everything after a ``#`` is a comment: 

15 

16.. code-block:: text 

17 

18 # This is a comment 

19 this is not a comment # but this is 

20 other stuff 

21 

22A word starting with ``!`` is a negative match, meaning that if the rest of 

23the word matches, then the relay should **not** be measured. Normally a match 

24means *yes, do measure*. 

25 

26.. code-block:: text 

27 

28 !DoNotMeasureThisFP 

29 DoMeasureThisFP 

30 

31A word containing a ``*`` is a wildcard word, meaning it matches all 

32fingerprints. 

33 

34.. code-block:: text 

35 

36 * # means measure all relays 

37 !* # means do not measure any relay 

38 

39Relay fingerprints are the only other valid non-comment text that should be in 

40this type of file. 

41 

42.. warning:: 

43 You may find that non-fingerprint text is parsed as fingerprints if they 

44 are not in a comment. Be careful. Don't do this. 

45 

46You can have multiple words/fingerprints per line. These snippets are parsed 

47the exactly same way. They demonstrate a config where 3 relays have opted-in to 

48being measured and no other relay should be measured. 

49 

50.. code-block:: text 

51 

52 # First 

53 RelayFP1 

54 RelayFP2 

55 RelayFP3 

56 !* 

57 

58 # Second 

59 RelayFP1 RelayFP2 RelayFP3 

60 !* 

61 

62 # Third 

63 RelayFP1 RelayFP2 RelayFP3 !* 

64 

65 RelayFP1 RelayFP2 RelayFP3 !* # Forth 

66 

67 

68Examples 

69-------- 

70 

71.. note:: 

72 In these examples, pretend that relay fingerprints are for alphanumeric 

73 characters. 

74 

75Do not measure any relay, ever:: 

76 

77 !* 

78 

79Measure all relays except one:: 

80 

81 !FFFF 

82 * 

83 

84Only measure one relay:: 

85 

86 AAAA !* 

87 

88Maybe two people have opted in to be measured and you want to organize their 

89relays by their families:: 

90 

91 # Jacob's relays 

92 AAAA BBBB CCCC 

93 DDDD EEEE FFFF 

94 

95 # Paul's relays 

96 PPPP1 PPPP2 PPPP3 

97 # Paul said we shouldn't measure this one 

98 !PPPP4 

99 

100 !* 

101 

102''' 

103from typing import List 

104 

105 

106def normalize_fp(s: str) -> str: 

107 ''' Normalize a fingerprint so no matter the format in which it is 

108 received, it will be in a consistent format for later comparisons to 

109 work. 

110 

111 Currently this function is used for things that may not be exactly a 

112 fingerprint: comment lines, and wildcard words are two examples. Don't edit 

113 this function without verifying this is no longer the case or that what you 

114 want to do won't break those other things. 

115 ''' 

116 return s.strip().lower() 

117 

118 

119class RFLWord: 

120 ''' A single word read from a file. You should not not need to use this 

121 directly. ''' 

122 #: Whether this is a negative-match word or not. If ``True``, then if the 

123 #: fingerprint matches, the relay should **NOT** be measured. 

124 is_negative: bool = False 

125 #: Whether this is a wildcard word or not. If ``True``, then this word 

126 #: matches all fingerprints. 

127 is_wildcard: bool = False 

128 

129 def __init__(self, s: str): 

130 # Perhaps a redundant action, but better safe than sorry. 

131 s = normalize_fp(s) 

132 assert not s.startswith('#') 

133 # Determine if this is a negative-match word, and remove the '!' if 

134 # needed 

135 self.is_negative = False 

136 if s.startswith('!'): 

137 self.is_negative = True 

138 s = s[1:] 

139 # Determine if this is a wildcard word, and remove the '*' if needed 

140 self.is_wildcard = False 

141 if s.startswith('*'): 

142 self.is_wildcard = True 

143 s = s[1:] 

144 # Save what remains of the word 

145 self.s = s 

146 

147 def matches(self, s: str) -> bool: 

148 ''' Determine if the given string matches this :class:`RFLWord` ''' 

149 if self.is_wildcard: 

150 return True 

151 return normalize_fp(s) == self.s 

152 

153 

154class RelayFilterList: 

155 #: Ordered list of word we read from the file 

156 words: List[RFLWord] 

157 

158 def __init__(self): 

159 self.words = [] 

160 

161 @staticmethod 

162 def from_str(s: str) -> 'RelayFilterList': 

163 ''' Given the entire string contents of a file, return a new 

164 :class:`RelayFilterList` 

165 ''' 

166 filter_list = RelayFilterList() 

167 for line in s.split('\n'): 

168 line = normalize_fp(line) 

169 # Remove both entire comment lines and end-of-line comments. 

170 if '#' in line: 

171 line = line[:line.find('#')] 

172 assert '#' not in line 

173 # Non-comment line. If empty, nothing to do 

174 if not len(line): 

175 continue 

176 # To support multiple fingerprints per line, act on each word 

177 # individually 

178 for word in line.split(): 

179 filter_list.words.append(RFLWord(normalize_fp(word))) 

180 return filter_list 

181 

182 def should_measure(self, fp: str, default: bool) -> bool: 

183 ''' Determine whether or not the given ``fp`` should be measured. If no 

184 match is found, then return ``default`` ''' 

185 for word in self.words: 

186 if word.matches(fp): 

187 return not word.is_negative 

188 return default