A good firewall matcher test suite should cover several important categories. You are given a list of proposed single-IP test cases. Each test case is (rules, ip_address), where rules is a list of (action, cidr) pairs. Return which coverage categories are still missing from the suite. Use these exact categories and this exact output order: ['SIMPLE_MATCH', 'SIMPLE_NON_MATCH', 'FIRST_RULE_WINS', 'BOUNDARY', 'EDGE_PREFIX_0', 'EDGE_PREFIX_32', 'INVALID_INPUT']. A case covers SIMPLE_MATCH if exactly one valid rule matches the IP. It covers SIMPLE_NON_MATCH if no valid rule matches and the result would be default deny. It covers FIRST_RULE_WINS if at least two valid rules match the same IP and at least two of those matching rules have different actions. It covers BOUNDARY if the IP equals the first or last IP of any valid CIDR block in that case. It covers EDGE_PREFIX_0 or EDGE_PREFIX_32 if that case contains a valid /0 or /32 rule. It covers INVALID_INPUT if the IP or any rule in that case is malformed. Return the missing categories.
Examples
Input: ([([('ALLOW', '10.0.0.0/24')], '10.0.0.5'), ([('ALLOW', '10.0.0.0/24')], '11.0.0.1'), ([('DENY', '10.0.0.0/8'), ('ALLOW', '10.0.0.0/16')], '10.0.1.1'), ([('ALLOW', '192.168.1.0/24')], '192.168.1.255'), ([('ALLOW', '0.0.0.0/0')], '8.8.8.8'), ([('DENY', '1.2.3.4/32')], '1.2.3.4'), ([('ALLOW', '300.1.1.0/24')], '1.1.1.1')],)
Expected Output: []
Explanation: This suite covers every required category, including invalid input.
Input: ([([('ALLOW', '10.0.0.0/24')], '10.0.0.5'), ([('ALLOW', '10.0.0.0/24')], '11.0.0.1'), ([('DENY', '10.0.0.0/8'), ('ALLOW', '10.0.0.0/16')], '10.0.1.1'), ([('ALLOW', '0.0.0.0/0')], '8.8.8.8')],)
Expected Output: ['BOUNDARY', 'EDGE_PREFIX_32', 'INVALID_INPUT']
Explanation: The suite has a simple match, a non-match, precedence, and /0, but no boundary case, no /32 case, and no invalid input case.
Input: ([],)
Expected Output: ['SIMPLE_MATCH', 'SIMPLE_NON_MATCH', 'FIRST_RULE_WINS', 'BOUNDARY', 'EDGE_PREFIX_0', 'EDGE_PREFIX_32', 'INVALID_INPUT']
Explanation: An empty proposed suite covers nothing.
Input: ([([('ALLOW', '10.0.0.0/24')], '999.1.1.1')],)
Expected Output: ['SIMPLE_MATCH', 'SIMPLE_NON_MATCH', 'FIRST_RULE_WINS', 'BOUNDARY', 'EDGE_PREFIX_0', 'EDGE_PREFIX_32']
Explanation: This suite covers only INVALID_INPUT because the queried IP is malformed.
Solution
def solution(test_suite):
required = [
'SIMPLE_MATCH',
'SIMPLE_NON_MATCH',
'FIRST_RULE_WINS',
'BOUNDARY',
'EDGE_PREFIX_0',
'EDGE_PREFIX_32',
'INVALID_INPUT',
]
covered = set()
def parse_ip(text):
if not isinstance(text, str):
return None
parts = text.split('.')
if len(parts) != 4:
return None
value = 0
for part in parts:
if not part.isdigit():
return None
num = int(part)
if num < 0 or num > 255:
return None
value = (value << 8) | num
return value
def parse_cidr(text):
if not isinstance(text, str) or text.count('/') != 1:
return None
ip_text, prefix_text = text.split('/')
if not prefix_text.isdigit():
return None
prefix = int(prefix_text)
if prefix < 0 or prefix > 32:
return None
ip_value = parse_ip(ip_text)
if ip_value is None:
return None
mask = 0 if prefix == 0 else ((0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF)
start = ip_value & mask
end = start | (0xFFFFFFFF ^ mask)
return start, end, prefix
for case in test_suite:
if not isinstance(case, (list, tuple)) or len(case) != 2:
covered.add('INVALID_INPUT')
continue
rules, ip_address = case
if not isinstance(rules, (list, tuple)):
covered.add('INVALID_INPUT')
continue
ip_value = parse_ip(ip_address)
if ip_value is None:
covered.add('INVALID_INPUT')
continue
valid_rules = []
bad_case = False
for rule in rules:
if not isinstance(rule, (list, tuple)) or len(rule) != 2:
bad_case = True
break
action, cidr = rule
if action not in ('ALLOW', 'DENY'):
bad_case = True
break
parsed = parse_cidr(cidr)
if parsed is None:
bad_case = True
break
start, end, prefix = parsed
valid_rules.append((action, start, end, prefix))
if bad_case:
covered.add('INVALID_INPUT')
continue
for _, start, end, prefix in valid_rules:
if prefix == 0:
covered.add('EDGE_PREFIX_0')
if prefix == 32:
covered.add('EDGE_PREFIX_32')
if ip_value == start or ip_value == end:
covered.add('BOUNDARY')
matched_actions = []
for action, start, end, _ in valid_rules:
if start <= ip_value <= end:
matched_actions.append(action)
if len(matched_actions) == 0:
covered.add('SIMPLE_NON_MATCH')
elif len(matched_actions) == 1:
covered.add('SIMPLE_MATCH')
elif len(set(matched_actions)) > 1:
covered.add('FIRST_RULE_WINS')
return [name for name in required if name not in covered]