Last active
June 23, 2022 23:51
-
-
Save lucaspar/57efb9ea11c4d83919b5e8e92d1e15f5 to your computer and use it in GitHub Desktop.
MAC address finder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Method to find MAC address in text (find_mac_address()) and testing routine.""" | |
from dataclasses import dataclass | |
from typing import List, Optional, Set | |
import re | |
import random | |
RANDOM_SEED = 42 | |
RND = random.Random(RANDOM_SEED) | |
def main(): | |
"""Main function.""" | |
print("QUICK TEST:") | |
test_find_mac_address(num_of_macs=20) | |
print("\nLONG TEST:") | |
test_find_mac_address() | |
print("TEST PASSED") | |
def find_mac_address(text: str) -> List[str]: | |
"""Finds MAC addresses in text. | |
It assumes MAC addresses separated by dashes or colons and it's of lower, upper, or mixed case: | |
xX:xX:XX:xx:XX:xX or XX-xX-XX-Xx-XX-XX. | |
Args: | |
text: text to find MAC addresses in. | |
""" | |
regex_matcher_str_colons = "([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})" | |
regex_matcher_str_dashes = "([0-9a-fA-F]{2}[-]){5}([0-9a-fA-F]{2})" | |
either_one = "|".join([regex_matcher_str_colons, regex_matcher_str_dashes]) | |
regex_matcher = re.compile(either_one) | |
matches = regex_matcher.finditer(text) | |
list_all_matches = list() | |
for match in matches: | |
list_all_matches.append(match.group()) | |
return list_all_matches | |
def test_find_mac_address(num_of_macs: int = 10_000) -> None: | |
"""Tests find_mac_address(). | |
Args: | |
target_num_macs: number of MAC addresses to generate and find. | |
""" | |
# generate random text with mac addresses to be found: | |
possible_mac_separators = set(":-") | |
text_gen = RandomTextGenerator(symbols_to_exclude=possible_mac_separators) | |
set_of_macs_to_find = { | |
get_random_mac(separator="random", case="random") for _ in range(num_of_macs) | |
} | |
random_texts = { | |
text_gen.random_text( | |
min_length=200, max_length=300, newline_prob=1 / 50, space_prob=1 / 6 | |
) | |
for _ in range(num_of_macs) | |
} | |
random_texts_with_mac = text_gen.random_text() + "".join( | |
[f"{mac}{text}" for mac, text in zip(set_of_macs_to_find, random_texts)] | |
) | |
macs_found = find_mac_address(random_texts_with_mac) | |
print_things = num_of_macs < 50 | |
if print_things: | |
__indented_random_texts_with_mac = "\n".join( | |
["\t" + line for line in random_texts_with_mac.split("\n")] | |
) | |
print( | |
"RANDOM TEXT WITH MACS::\n\n{}\n".format(__indented_random_texts_with_mac) | |
) | |
# find MAC addresses in random texts: | |
print("MAC ADDRESSES FOUND:\n") | |
for mac in macs_found: | |
print("\t{}".format(mac)) | |
print("") | |
# make sure all MAC addresses were found: | |
set_of_macs_found = set(macs_found) | |
found_but_not_expected = set_of_macs_found - set_of_macs_to_find | |
expected_but_not_found = set_of_macs_to_find - set_of_macs_found | |
mistakes = found_but_not_expected | expected_but_not_found | |
assert ( | |
len(macs_found) == len(set_of_macs_to_find) | |
and set_of_macs_found == set_of_macs_to_find | |
), ( | |
"Not all MAC addresses were found:\n" | |
+ "\tFound but not expected: {}".format(found_but_not_expected) | |
+ "\n" | |
+ "\tExpected but not found: {}".format(expected_but_not_found) | |
) | |
# if set is empty | |
if len(mistakes) == 0: | |
print("All {} MAC addresses were found.".format(len(set_of_macs_to_find))) | |
else: | |
raise ValueError("Not all MAC addresses were found: {}".format(mistakes)) | |
def get_random_mac(separator: str = ":", case: str = "upper") -> str: | |
"""Generates a random MAC address.""" | |
# argument checks | |
lowercase_values = {"lower", "lowercase"} | |
uppercase_values = {"upper", "uppercase"} | |
if isinstance(case, str): | |
assert case in lowercase_values | uppercase_values | { | |
"random" | |
}, "Invalid value for case: '{}'".format(case) | |
assert ( | |
len(separator) == 1 or separator == "random" | |
), "Invalid separator: '{}'".format(separator) | |
if separator == "random": | |
separator = RND.choice(["-", ":"]) | |
mac = [RND.randint(0x00, 0x7F) for _ in range(6)] | |
# format MAC address: | |
mac_address = separator.join(map("{:02x}".format, mac)) | |
# set case | |
is_lowercase = RND.random() < 0.5 if case == "random" else case in lowercase_values | |
mac_address = mac_address.lower() if is_lowercase else mac_address.upper() | |
return mac_address | |
@dataclass | |
class RandomTextGenerator: | |
"""Helper methods for randomly generated text.""" | |
_bag_of_chars: Optional[Set[str]] = None | |
_random_seed: int = RANDOM_SEED | |
include_lowercase: bool = True | |
include_numbers: bool = True | |
include_symbols: bool = True | |
include_uppercase: bool = True | |
symbols_to_exclude: Optional[Set] = None | |
def __post_init__(self): | |
self._bag_of_chars = self._generate_bag_of_characters() | |
self._rnd = random.Random(self._random_seed) | |
def _generate_bag_of_characters(self) -> Set[str]: | |
"""Generates a bag of characters.""" | |
bag_of_chars = set() | |
if self.include_symbols: | |
bag_of_chars.update(set("!@#$%^&*()_+-=[]{}|;':,./<>?`~")) | |
if self.include_numbers: | |
bag_of_chars.update(set("0123456789")) | |
if self.include_lowercase: | |
bag_of_chars.update(set("abcdefghijklmnopqrstuvwxyz")) | |
if self.include_uppercase: | |
bag_of_chars.update(set("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) | |
if self.symbols_to_exclude is not None: | |
bag_of_chars -= self.symbols_to_exclude | |
assert len(bag_of_chars) > 0, "No characters to choose from." | |
return bag_of_chars | |
def random_text( | |
self, | |
min_length: int = 10, | |
max_length: int = 25, | |
space_prob: float = 0, | |
newline_prob: float = 0, | |
) -> str: | |
"""Generates a random text. | |
Args: | |
min_length: Minimum length of the text. | |
max_length: Maximum length of the text. | |
space_prob: Probability of a space character. | |
newline_prob: Probability of a newline character. | |
""" | |
max_length_override = max(min_length, max_length) | |
min_length_override = min(min_length, max_length) | |
target_length = self._rnd.randint(min_length_override, max_length_override) | |
assert 0 <= space_prob < 1, "Space probability is out of range: {}".format( | |
space_prob | |
) | |
assert 0 <= newline_prob < 1, "Newline probability is out of range: {}".format( | |
newline_prob | |
) | |
assert ( | |
target_length >= min_length_override | |
and target_length <= max_length_override | |
), "Random length is out of range: {}".format(target_length) | |
assert self._bag_of_chars is not None, "Bag of characters is not initialized." | |
# define characters to choose from | |
random_text = "" | |
while len(random_text) < target_length: | |
# add single character | |
if self._rnd.random() < newline_prob: | |
random_text += "\n" | |
else: | |
random_text += self._rnd.choice(list(self._bag_of_chars)) | |
assert ( | |
len(random_text) == target_length | |
), "Length of random text is not correct: got {} instead of {}".format( | |
len(random_text), target_length | |
) | |
return random_text | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment