Initial commit: Dabit Time Manager project

Python-based time management application with UDP discovery,
TCP protocol communication, time sync, and drift monitoring.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
insulee
2026-02-10 11:10:55 +09:00
commit 3c14e1e401
27 changed files with 2240 additions and 0 deletions

88
network/packet_parser.py Normal file
View File

@@ -0,0 +1,88 @@
"""DIBD 응답 패킷 파싱 (가변 길이 지원)"""
from models.controller import Controller
from config import PROTOCOL_ENCODING
def parse_dibd_packet(data: bytes) -> Controller | None:
"""DIBD 응답 패킷을 파싱하여 Controller 객체를 반환.
패킷 길이:
- 이더넷 전용 모델: 174바이트 (DNS Name까지)
- WiFi 지원 모델: 222바이트 (AP Mode까지)
패킷 구조 (0x88 응답, \r\n으로 필드 구분):
0-5: 'DIBD\\r\\n'
6-24: MAC (17자) + \\r\\n
25-41: Local IP (15자) + \\r\\n
42-58: Subnet Mask (15자) + \\r\\n
59-75: Gateway IP (15자) + \\r\\n
76-82: Port (5자) + \\r\\n
83-86: DHCP Mode (2자) + \\r\\n
87-103: Server IP (15자) + \\r\\n
104-110: Server Port (5자) + \\r\\n
111-114: Server Mode (2자) + \\r\\n
115-136: Name (20자) + \\r\\n
137-141: KeepAlive (3자) + \\r\\n
142-173: DNS Name (30자) + \\r\\n ← 174바이트 여기까지
174-195: AP SSID Name (20자) + \\r\\n ← 선택
196-217: AP SSID PW (20자) + \\r\\n ← 선택
218-221: AP Mode (2자) + \\r\\n ← 선택
"""
try:
text = data.decode(PROTOCOL_ENCODING, errors="replace")
except Exception:
return None
# 최소 길이: KeepAlive 필드까지 = 142바이트
if len(text) < 142:
return None
if not text.startswith("DIBD"):
return None
ctrl = Controller()
ctrl.mac = text[6:23].strip()
ctrl.ip = _normalize_ip(text[25:40].strip())
ctrl.subnet = _normalize_ip(text[42:57].strip())
ctrl.gateway = _normalize_ip(text[59:74].strip())
ctrl.port = _safe_int(text[76:81].strip(), 5000)
ctrl.dhcp_mode = _safe_int(text[83:85].strip(), 30) % 30
ctrl.server_ip = _normalize_ip(text[87:102].strip())
ctrl.server_port = _safe_int(text[104:109].strip(), 0)
ctrl.server_mode = _safe_int(text[111:113].strip(), 30) % 30
ctrl.name = text[115:135].strip()
ctrl.keep_alive = _safe_int(text[137:140].strip(), 0)
# DNS Name (선택 - 174바이트 이상)
if len(text) >= 172:
ctrl.dns_name = text[142:172].strip()
# AP SSID (선택 - 222바이트 패킷)
if len(text) >= 194:
ctrl.ap_ssid_name = text[174:194].strip()
if len(text) >= 216:
ctrl.ap_ssid_pw = text[196:216].strip()
if len(text) >= 220:
ctrl.ap_mode = _safe_int(text[218:220].strip(), 30) % 30
return ctrl
def _normalize_ip(ip_str: str) -> str:
"""패딩된 IP(예: '192.168.000.074')를 일반형(예: '192.168.0.74')으로 변환."""
try:
parts = ip_str.split(".")
return ".".join(str(int(p)) for p in parts)
except (ValueError, IndexError):
return ip_str
def _safe_int(s: str, default: int = 0) -> int:
try:
return int(s)
except (ValueError, TypeError):
return default