Coverage for /root/GitHubProjects/impacket/impacket/IP6_Address.py: 83%

# Coverage for /root/GitHubProjects/impacket/impacket/IP6_Address.py : 83%

## 152 statements

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

8 import array

9 from six import string_types

10

11 class 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