92 lines
2.3 KiB
Markdown
92 lines
2.3 KiB
Markdown
|
A
|
# Unit 03 — Utilities
|
||
|
|
|
||
|
|
## Goal
|
||
|
|
Shared helper functions used across server modules. No business logic — pure helpers.
|
||
|
|
|
||
|
|
## File
|
||
|
|
- `server/utils.py`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Functions
|
||
|
|
|
||
|
|
### `validate_mac(mac: str) -> bool`
|
||
|
|
```python
|
||
|
|
import re
|
||
|
|
MAC_RE = re.compile(r'^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')
|
||
|
|
def validate_mac(mac: str) -> bool:
|
||
|
|
return bool(MAC_RE.match(mac)) if mac else False
|
||
|
|
```
|
||
|
|
|
||
|
|
### `normalize_mac(mac: str) -> str`
|
||
|
|
Lowercase, colon-separated. Input may be with dashes or no separator.
|
||
|
|
```python
|
||
|
|
def normalize_mac(mac: str) -> str:
|
||
|
|
clean = mac.lower().replace('-', ':').replace('.', ':')
|
||
|
|
# handle no-separator format (12 hex chars)
|
||
|
|
if ':' not in clean and len(clean) == 12:
|
||
|
|
clean = ':'.join(clean[i:i+2] for i in range(0, 12, 2))
|
||
|
|
return clean
|
||
|
|
```
|
||
|
|
|
||
|
|
### `validate_imsi(imsi: str) -> bool`
|
||
|
|
```python
|
||
|
|
def validate_imsi(imsi: str) -> bool:
|
||
|
|
return bool(imsi) and imsi.isdigit() and len(imsi) == 15
|
||
|
|
```
|
||
|
|
|
||
|
|
### `load_config(path: str) -> dict`
|
||
|
|
```python
|
||
|
|
import yaml, os
|
||
|
|
|
||
|
|
REQUIRED_KEYS = ['network', 'server', 'database', 'rate_limiting', 'profile']
|
||
|
|
|
||
|
|
def load_config(path: str) -> dict:
|
||
|
|
if not os.path.exists(path):
|
||
|
|
raise FileNotFoundError(f"Config file not found: {path}")
|
||
|
|
with open(path) as f:
|
||
|
|
cfg = yaml.safe_load(f)
|
||
|
|
for key in REQUIRED_KEYS:
|
||
|
|
if key not in cfg:
|
||
|
|
raise ValueError(f"Missing required config section: {key}")
|
||
|
|
return cfg
|
||
|
|
```
|
||
|
|
|
||
|
|
### `hex_to_bytes(h: str) -> bytes`
|
||
|
|
```python
|
||
|
|
def hex_to_bytes(h: str) -> bytes:
|
||
|
|
return bytes.fromhex(h)
|
||
|
|
```
|
||
|
|
|
||
|
|
### `bytes_to_hex(b: bytes) -> str`
|
||
|
|
```python
|
||
|
|
def bytes_to_hex(b: bytes) -> str:
|
||
|
|
return b.hex()
|
||
|
|
```
|
||
|
|
|
||
|
|
### `get_client_ip(request) -> str`
|
||
|
|
Flask request helper — returns real IP behind proxy:
|
||
|
|
```python
|
||
|
|
def get_client_ip(request) -> str:
|
||
|
|
if request.headers.get('X-Forwarded-For'):
|
||
|
|
return request.headers['X-Forwarded-For'].split(',')[0].strip()
|
||
|
|
return request.remote_addr
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Verification
|
||
|
|
```bash
|
||
|
|
python -c "
|
||
|
|
from server.utils import validate_mac, normalize_mac, validate_imsi, load_config
|
||
|
|
|
||
|
|
assert validate_mac('aa:bb:cc:dd:ee:ff') == True
|
||
|
|
assert validate_mac('AA:BB:CC:DD:EE:FF') == True
|
||
|
|
assert validate_mac('bad') == False
|
||
|
|
assert normalize_mac('AABBCCDDEEFF') == 'aa:bb:cc:dd:ee:ff'
|
||
|
|
assert validate_imsi('302720123456789') == True
|
||
|
|
assert validate_imsi('12345') == False
|
||
|
|
print('All checks passed')
|
||
|
|
"
|
||
|
|
```
|