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# Impacket - Collection of Python classes for working with network protocols.

2#

3# SECUREAUTH LABS. Copyright (C) 2018 SecureAuth Corporation. All rights reserved.

4#

5# This software is provided under a slightly modified version

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

7# for more information.

8#

9

10 import array

11 from six import string_types

12

13 class IP6_Address:

14 ADDRESS_BYTE_SIZE = 16

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

16 TOTAL_HEX_GROUPS = 8

17 HEX_GROUP_SIZE = 4 #Size in characters

18 TOTAL_SEPARATORS = TOTAL_HEX_GROUPS - 1

19 ADDRESS_TEXT_SIZE = (TOTAL_HEX_GROUPS * HEX_GROUP_SIZE) + TOTAL_SEPARATORS

20 SEPARATOR = ":"

21 SCOPE_SEPARATOR = "%"

22

23#############################################################################################################

24# Constructor and construction helpers

25

26 def __init__(self, address):

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

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

29 self.__scope_id = ""

30

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

32 if isinstance(address, string_types):

33 self.__from_string(address)

34 else:

35 self.__from_bytes(address)

36

37

38 def __from_string(self, address):

39 #Separate the Scope ID, if present

40 if self.__is_a_scoped_address(address):

41 split_parts = address.split(self.SCOPE_SEPARATOR)

42 address = split_parts[0]

43 if split_parts[1] == "":

44 raise Exception("Empty scope ID")

45 self.__scope_id = split_parts[1]

46

47 #Expand address if it's in compressed form

48 if self.__is_address_in_compressed_form(address):

49 address = self.__expand_compressed_address(address)

50

51 #Insert leading zeroes where needed

52 address = self.__insert_leading_zeroes(address)

53

54 #Sanity check

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

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

57

58 #Split address into hex groups

59 hex_groups = address.split(self.SEPARATOR)

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

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

62

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

64 offset = 0

65 for group in hex_groups:

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

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

68

69 group_as_int = int(group, 16)

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

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

72 offset += 2

73

74 def __from_bytes(self, theBytes):

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

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

77 self.__bytes = theBytes

78

79#############################################################################################################

80# Projectors

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

82 s = ""

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

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

85 if i % 2 == 1:

86 s += self.SEPARATOR

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

88

89 if compress_address:

90 s = self.__trim_leading_zeroes(s)

91 s = self.__trim_longest_zero_chain(s)

92

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

94 s += self.SCOPE_SEPARATOR + self.__scope_id

95 return s

96

97 def as_bytes(self):

98 return self.__bytes

99

100 def __str__(self):

101 return self.as_string()

102

103 def get_scope_id(self):

104 return self.__scope_id

105

106 def get_unscoped_address(self):

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

108

109#############################################################################################################

110# Semantic helpers

111 def is_multicast(self):

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

113

114 def is_unicast(self):

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

116

117 def is_link_local_unicast(self):

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

119

120 def is_site_local_unicast(self):

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

122

123 def is_unique_local_unicast(self):

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

125

126

127 def get_human_readable_address_type(self):

128 if self.is_multicast():

129 return "multicast"

130 elif self.is_unicast():

131 if self.is_link_local_unicast():

132 return "link-local unicast"

133 elif self.is_site_local_unicast():

134 return "site-local unicast"

135 else:

136 return "unicast"

137 elif self.is_unique_local_unicast():

138 return "unique-local unicast"

139 else:

140 return "unknown type"

141

142#############################################################################################################

143#Expansion helpers

144

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

146 def __is_address_in_compressed_form(self, address):

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

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

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

150

151 #Count the double colon marker

152 compression_marker_count = self.__count_compression_marker(address)

153 if compression_marker_count == 0:

154 return False

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

156 return True

157 else:

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

159

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

161 def __count_compressed_groups(self, address):

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

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

164

165 #Counts how many compression markers are present

166 def __count_compression_marker(self, address):

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

168

169 #Inserts leading zeroes in every hex group

170 def __insert_leading_zeroes(self, address):

171 hex_groups = address.split(self.SEPARATOR)

172

173 new_address = ""

174 for hex_group in hex_groups:

175 if len(hex_group) < 4:

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

177 new_address += hex_group + self.SEPARATOR

178

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

180

181

182 #Expands a compressed address

183 def __expand_compressed_address(self, address):

184 group_count = self.__count_compressed_groups(address)

185 groups_to_insert = self.TOTAL_HEX_GROUPS - group_count

186

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

188 while groups_to_insert:

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

190 pos += 5

191 groups_to_insert -= 1

192

193 #Replace the compression marker with a single colon

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

195 return address

196

197

198#############################################################################################################

199#Compression helpers

200

201 def __trim_longest_zero_chain(self, address):

202 chain_size = 8

203

204 while chain_size > 0:

205 groups = address.split(self.SEPARATOR)

206

207 for index, group in enumerate(groups):

208 #Find the first zero

209 if group == "0":

210 start_index = index

211 end_index = index

212 #Find the end of this chain of zeroes

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

214 end_index += 1

215

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

217 found_size = end_index - start_index + 1

218 if found_size == chain_size:

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

220 return address

221

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

223 chain_size -= 1

224 return address

225

226

227 #Trims all leading zeroes from every hex group

228 def __trim_leading_zeroes(self, theStr):

229 groups = theStr.split(self.SEPARATOR)

230 theStr = ""

231

232 for group in groups:

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

234 if group == self.SEPARATOR:

235 group = "0" + self.SEPARATOR

236 theStr += group

237 return theStr[:-1]

238

239

240#############################################################################################################

241 @classmethod

242 def is_a_valid_text_representation(cls, text_representation):

243 try:

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

245 IP6_Address(text_representation)

246 return True

247 except Exception:

248 return False

249

250 def __is_a_scoped_address(self, text_representation):

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