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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

2# 

3# This software is provided under under a slightly modified version 

4# of the Apache Software License. See the accompanying LICENSE file 

5# for more information. 

6# 

7 

8import array 

9from six import string_types 

10 

11class IP6_Address: 

12 ADDRESS_BYTE_SIZE = 16 

13 #A Hex Group is a 16-bit unit of the address 

14 TOTAL_HEX_GROUPS = 8 

15 HEX_GROUP_SIZE = 4 #Size in characters 

16 TOTAL_SEPARATORS = TOTAL_HEX_GROUPS - 1 

17 ADDRESS_TEXT_SIZE = (TOTAL_HEX_GROUPS * HEX_GROUP_SIZE) + TOTAL_SEPARATORS 

18 SEPARATOR = ":" 

19 SCOPE_SEPARATOR = "%" 

20 

21############################################################################################################# 

22# Constructor and construction helpers 

23 

24 def __init__(self, address): 

25 #The internal representation of an IP6 address is a 16-byte array 

26 self.__bytes = array.array('B', b'\0' * self.ADDRESS_BYTE_SIZE) 

27 self.__scope_id = "" 

28 

29 #Invoke a constructor based on the type of the argument 

30 if isinstance(address, string_types): 

31 self.__from_string(address) 

32 else: 

33 self.__from_bytes(address) 

34 

35 

36 def __from_string(self, address): 

37 #Separate the Scope ID, if present 

38 if self.__is_a_scoped_address(address): 

39 split_parts = address.split(self.SCOPE_SEPARATOR) 

40 address = split_parts[0] 

41 if split_parts[1] == "": 

42 raise Exception("Empty scope ID") 

43 self.__scope_id = split_parts[1] 

44 

45 #Expand address if it's in compressed form 

46 if self.__is_address_in_compressed_form(address): 

47 address = self.__expand_compressed_address(address) 

48 

49 #Insert leading zeroes where needed  

50 address = self.__insert_leading_zeroes(address) 

51 

52 #Sanity check 

53 if len(address) != self.ADDRESS_TEXT_SIZE: 

54 raise Exception('IP6_Address - from_string - address size != ' + str(self.ADDRESS_TEXT_SIZE)) 

55 

56 #Split address into hex groups 

57 hex_groups = address.split(self.SEPARATOR) 

58 if len(hex_groups) != self.TOTAL_HEX_GROUPS: 

59 raise Exception('IP6_Address - parsed hex groups != ' + str(self.TOTAL_HEX_GROUPS)) 

60 

61 #For each hex group, convert it into integer words 

62 offset = 0 

63 for group in hex_groups: 

64 if len(group) != self.HEX_GROUP_SIZE: 64 ↛ 65line 64 didn't jump to line 65, because the condition on line 64 was never true

65 raise Exception('IP6_Address - parsed hex group length != ' + str(self.HEX_GROUP_SIZE)) 

66 

67 group_as_int = int(group, 16) 

68 self.__bytes[offset] = (group_as_int & 0xFF00) >> 8 

69 self.__bytes[offset + 1] = (group_as_int & 0x00FF) 

70 offset += 2 

71 

72 def __from_bytes(self, theBytes): 

73 if len(theBytes) != self.ADDRESS_BYTE_SIZE: 

74 raise Exception ("IP6_Address - from_bytes - array size != " + str(self.ADDRESS_BYTE_SIZE)) 

75 self.__bytes = theBytes 

76 

77############################################################################################################# 

78# Projectors 

79 def as_string(self, compress_address = True, scoped_address = True): 

80 s = "" 

81 for i, v in enumerate(self.__bytes): 

82 s += hex(v)[2:].rjust(2, '0') 

83 if i % 2 == 1: 

84 s += self.SEPARATOR 

85 s = s[:-1].upper() 

86 

87 if compress_address: 

88 s = self.__trim_leading_zeroes(s) 

89 s = self.__trim_longest_zero_chain(s) 

90 

91 if scoped_address and self.get_scope_id() != "": 

92 s += self.SCOPE_SEPARATOR + self.__scope_id 

93 return s 

94 

95 def as_bytes(self): 

96 return self.__bytes 

97 

98 def __str__(self): 

99 return self.as_string() 

100 

101 def get_scope_id(self): 

102 return self.__scope_id 

103 

104 def get_unscoped_address(self): 

105 return self.as_string(True, False) #Compressed address = True, Scoped address = False 

106 

107############################################################################################################# 

108# Semantic helpers 

109 def is_multicast(self): 

110 return self.__bytes[0] == 0xFF 

111 

112 def is_unicast(self): 

113 return self.__bytes[0] == 0xFE 

114 

115 def is_link_local_unicast(self): 

116 return self.is_unicast() and (self.__bytes[1] & 0xC0 == 0x80) 

117 

118 def is_site_local_unicast(self): 

119 return self.is_unicast() and (self.__bytes[1] & 0xC0 == 0xC0) 

120 

121 def is_unique_local_unicast(self): 

122 return self.__bytes[0] == 0xFD 

123 

124 

125 def get_human_readable_address_type(self): 

126 if self.is_multicast(): 

127 return "multicast" 

128 elif self.is_unicast(): 

129 if self.is_link_local_unicast(): 

130 return "link-local unicast" 

131 elif self.is_site_local_unicast(): 

132 return "site-local unicast" 

133 else: 

134 return "unicast" 

135 elif self.is_unique_local_unicast(): 

136 return "unique-local unicast" 

137 else: 

138 return "unknown type" 

139 

140############################################################################################################# 

141#Expansion helpers 

142 

143 #Predicate - returns whether an address is in compressed form 

144 def __is_address_in_compressed_form(self, address): 

145 #Sanity check - triple colon detection (not detected by searches of double colon)  

146 if address.count(self.SEPARATOR * 3) > 0: 

147 raise Exception('IP6_Address - found triple colon') 

148 

149 #Count the double colon marker 

150 compression_marker_count = self.__count_compression_marker(address) 

151 if compression_marker_count == 0: 

152 return False 

153 elif compression_marker_count == 1: 153 ↛ 156line 153 didn't jump to line 156, because the condition on line 153 was never false

154 return True 

155 else: 

156 raise Exception('IP6_Address - more than one compression marker (\"::\") found') 

157 

158 #Returns how many hex groups are present, in a compressed address  

159 def __count_compressed_groups(self, address): 

160 trimmed_address = address.replace(self.SEPARATOR * 2, self.SEPARATOR) #Replace "::" with ":"  

161 return trimmed_address.count(self.SEPARATOR) + 1 

162 

163 #Counts how many compression markers are present 

164 def __count_compression_marker(self, address): 

165 return address.count(self.SEPARATOR * 2) #Count occurrences of "::" 

166 

167 #Inserts leading zeroes in every hex group 

168 def __insert_leading_zeroes(self, address): 

169 hex_groups = address.split(self.SEPARATOR) 

170 

171 new_address = "" 

172 for hex_group in hex_groups: 

173 if len(hex_group) < 4: 

174 hex_group = hex_group.rjust(4, "0") 

175 new_address += hex_group + self.SEPARATOR 

176 

177 return new_address[:-1] #Trim the last colon 

178 

179 

180 #Expands a compressed address 

181 def __expand_compressed_address(self, address): 

182 group_count = self.__count_compressed_groups(address) 

183 groups_to_insert = self.TOTAL_HEX_GROUPS - group_count 

184 

185 pos = address.find(self.SEPARATOR * 2) + 1 

186 while groups_to_insert: 

187 address = address[:pos] + "0000" + self.SEPARATOR + address[pos:] 

188 pos += 5 

189 groups_to_insert -= 1 

190 

191 #Replace the compression marker with a single colon  

192 address = address.replace(self.SEPARATOR * 2, self.SEPARATOR) 

193 return address 

194 

195 

196############################################################################################################# 

197#Compression helpers 

198 

199 def __trim_longest_zero_chain(self, address): 

200 chain_size = 8 

201 

202 while chain_size > 0: 

203 groups = address.split(self.SEPARATOR) 

204 

205 for index, group in enumerate(groups): 

206 #Find the first zero 

207 if group == "0": 

208 start_index = index 

209 end_index = index 

210 #Find the end of this chain of zeroes 

211 while end_index < 7 and groups[end_index + 1] == "0": 

212 end_index += 1 

213 

214 #If the zero chain matches the current size, trim it 

215 found_size = end_index - start_index + 1 

216 if found_size == chain_size: 

217 address = self.SEPARATOR.join(groups[0:start_index]) + self.SEPARATOR * 2 + self.SEPARATOR.join(groups[(end_index+1):]) 

218 return address 

219 

220 #No chain of this size found, try with a lower size  

221 chain_size -= 1 

222 return address 

223 

224 

225 #Trims all leading zeroes from every hex group 

226 def __trim_leading_zeroes(self, theStr): 

227 groups = theStr.split(self.SEPARATOR) 

228 theStr = "" 

229 

230 for group in groups: 

231 group = group.lstrip("0") + self.SEPARATOR 

232 if group == self.SEPARATOR: 

233 group = "0" + self.SEPARATOR 

234 theStr += group 

235 return theStr[:-1] 

236 

237 

238############################################################################################################# 

239 @classmethod 

240 def is_a_valid_text_representation(cls, text_representation): 

241 try: 

242 #Capitalize on the constructor's ability to detect invalid text representations of an IP6 address  

243 IP6_Address(text_representation) 

244 return True 

245 except Exception: 

246 return False 

247 

248 def __is_a_scoped_address(self, text_representation): 

249 return text_representation.count(self.SCOPE_SEPARATOR) == 1