You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

395 lines
12 KiB

# P2P Network Communication - Data Models
"""
数据模型和枚举类型定义
包含消息类型、用户状态、传输状态等核心数据结构
"""
import hashlib
import json
from dataclasses import dataclass, field, asdict
from datetime import datetime
from enum import Enum
from typing import Optional
class MessageType(Enum):
"""消息类型枚举"""
TEXT = "text"
FILE_REQUEST = "file_request"
FILE_CHUNK = "file_chunk"
FILE_COMPLETE = "file_complete"
IMAGE = "image"
AUDIO_STREAM = "audio_stream"
VIDEO_STREAM = "video_stream"
VOICE_CALL_REQUEST = "voice_call_request"
VOICE_CALL_ACCEPT = "voice_call_accept"
VOICE_CALL_REJECT = "voice_call_reject"
VOICE_CALL_END = "voice_call_end"
VOICE_DATA = "voice_data"
HEARTBEAT = "heartbeat"
USER_REGISTER = "user_register"
USER_UNREGISTER = "user_unregister"
USER_LIST_REQUEST = "user_list_request"
USER_LIST_RESPONSE = "user_list_response"
ACK = "ack"
ERROR = "error"
class UserStatus(Enum):
"""用户状态枚举"""
ONLINE = "online"
OFFLINE = "offline"
BUSY = "busy"
AWAY = "away"
class TransferStatus(Enum):
"""传输状态枚举"""
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
PAUSED = "paused"
class ConnectionMode(Enum):
"""连接模式枚举"""
P2P = "p2p" # 局域网直连
RELAY = "relay" # 服务器中转
UNKNOWN = "unknown" # 未知
class NetworkQuality(Enum):
"""网络质量枚举"""
EXCELLENT = "excellent" # 延迟 < 50ms
GOOD = "good" # 延迟 50-100ms
FAIR = "fair" # 延迟 100-200ms
POOR = "poor" # 延迟 200-300ms
BAD = "bad" # 延迟 > 300ms
@dataclass
class Message:
"""消息数据结构"""
msg_type: MessageType
sender_id: str
receiver_id: str
timestamp: float
payload: bytes
checksum: str = field(default="")
message_id: str = field(default="")
def __post_init__(self):
"""初始化后计算校验和和消息ID"""
if not self.checksum:
self.checksum = self._calculate_checksum()
if not self.message_id:
self.message_id = self._generate_message_id()
def _calculate_checksum(self) -> str:
"""计算消息校验和"""
data = f"{self.msg_type.value}{self.sender_id}{self.receiver_id}{self.timestamp}"
data_bytes = data.encode('utf-8') + self.payload
return hashlib.md5(data_bytes).hexdigest()
def _generate_message_id(self) -> str:
"""生成消息ID"""
data = f"{self.sender_id}{self.receiver_id}{self.timestamp}{self.checksum}"
return hashlib.sha256(data.encode('utf-8')).hexdigest()[:32]
def verify_checksum(self) -> bool:
"""验证消息校验和"""
return self.checksum == self._calculate_checksum()
def to_dict(self) -> dict:
"""转换为字典"""
return {
"msg_type": self.msg_type.value,
"sender_id": self.sender_id,
"receiver_id": self.receiver_id,
"timestamp": self.timestamp,
"payload": self.payload.hex(), # bytes转hex字符串
"checksum": self.checksum,
"message_id": self.message_id,
}
@classmethod
def from_dict(cls, data: dict) -> "Message":
"""从字典创建Message对象"""
return cls(
msg_type=MessageType(data["msg_type"]),
sender_id=data["sender_id"],
receiver_id=data["receiver_id"],
timestamp=data["timestamp"],
payload=bytes.fromhex(data["payload"]),
checksum=data.get("checksum", ""),
message_id=data.get("message_id", ""),
)
def serialize(self) -> bytes:
"""序列化消息为字节流"""
return json.dumps(self.to_dict()).encode('utf-8')
@classmethod
def deserialize(cls, data: bytes) -> "Message":
"""反序列化字节流为消息"""
return cls.from_dict(json.loads(data.decode('utf-8')))
@dataclass
class UserInfo:
"""用户信息"""
user_id: str
username: str
display_name: str
status: UserStatus = UserStatus.OFFLINE
ip_address: str = ""
port: int = 0
last_seen: Optional[datetime] = None
public_key: bytes = field(default_factory=bytes)
def to_dict(self) -> dict:
"""转换为字典"""
return {
"user_id": self.user_id,
"username": self.username,
"display_name": self.display_name,
"status": self.status.value,
"ip_address": self.ip_address,
"port": self.port,
"last_seen": self.last_seen.isoformat() if self.last_seen else None,
"public_key": self.public_key.hex() if self.public_key else "",
}
@classmethod
def from_dict(cls, data: dict) -> "UserInfo":
"""从字典创建UserInfo对象"""
last_seen = None
if data.get("last_seen"):
last_seen = datetime.fromisoformat(data["last_seen"])
public_key = bytes()
if data.get("public_key"):
public_key = bytes.fromhex(data["public_key"])
return cls(
user_id=data["user_id"],
username=data["username"],
display_name=data["display_name"],
status=UserStatus(data.get("status", "offline")),
ip_address=data.get("ip_address", ""),
port=data.get("port", 0),
last_seen=last_seen,
public_key=public_key,
)
def serialize(self) -> bytes:
"""序列化为字节流"""
return json.dumps(self.to_dict()).encode('utf-8')
@classmethod
def deserialize(cls, data: bytes) -> "UserInfo":
"""反序列化字节流"""
return cls.from_dict(json.loads(data.decode('utf-8')))
@dataclass
class ChatMessage:
"""聊天消息记录"""
message_id: str
sender_id: str
receiver_id: str
content_type: MessageType
content: str # 文本内容或文件路径
timestamp: datetime = field(default_factory=datetime.now)
is_read: bool = False
is_sent: bool = False
def to_dict(self) -> dict:
"""转换为字典"""
return {
"message_id": self.message_id,
"sender_id": self.sender_id,
"receiver_id": self.receiver_id,
"content_type": self.content_type.value,
"content": self.content,
"timestamp": self.timestamp.isoformat(),
"is_read": self.is_read,
"is_sent": self.is_sent,
}
@classmethod
def from_dict(cls, data: dict) -> "ChatMessage":
"""从字典创建ChatMessage对象"""
return cls(
message_id=data["message_id"],
sender_id=data["sender_id"],
receiver_id=data["receiver_id"],
content_type=MessageType(data["content_type"]),
content=data["content"],
timestamp=datetime.fromisoformat(data["timestamp"]),
is_read=data.get("is_read", False),
is_sent=data.get("is_sent", False),
)
def serialize(self) -> bytes:
"""序列化为字节流"""
return json.dumps(self.to_dict()).encode('utf-8')
@classmethod
def deserialize(cls, data: bytes) -> "ChatMessage":
"""反序列化字节流"""
return cls.from_dict(json.loads(data.decode('utf-8')))
@dataclass
class FileChunk:
"""文件块数据结构"""
file_id: str
chunk_index: int
total_chunks: int
data: bytes
checksum: str = field(default="")
def __post_init__(self):
"""初始化后计算校验和"""
if not self.checksum:
self.checksum = hashlib.md5(self.data).hexdigest()
def verify_checksum(self) -> bool:
"""验证数据块校验和"""
return self.checksum == hashlib.md5(self.data).hexdigest()
def to_dict(self) -> dict:
"""转换为字典"""
return {
"file_id": self.file_id,
"chunk_index": self.chunk_index,
"total_chunks": self.total_chunks,
"data": self.data.hex(),
"checksum": self.checksum,
}
@classmethod
def from_dict(cls, data: dict) -> "FileChunk":
"""从字典创建FileChunk对象"""
return cls(
file_id=data["file_id"],
chunk_index=data["chunk_index"],
total_chunks=data["total_chunks"],
data=bytes.fromhex(data["data"]),
checksum=data.get("checksum", ""),
)
@dataclass
class TransferProgress:
"""传输进度信息"""
file_id: str
file_name: str
total_size: int
transferred_size: int
speed: float = 0.0 # bytes per second
eta: float = 0.0 # estimated time remaining in seconds
@property
def progress_percent(self) -> float:
"""获取进度百分比"""
if self.total_size == 0:
return 0.0
return (self.transferred_size / self.total_size) * 100
def to_dict(self) -> dict:
"""转换为字典"""
return {
"file_id": self.file_id,
"file_name": self.file_name,
"total_size": self.total_size,
"transferred_size": self.transferred_size,
"speed": self.speed,
"eta": self.eta,
"progress_percent": self.progress_percent,
}
@dataclass
class FileTransferRecord:
"""文件传输记录"""
transfer_id: str
file_name: str
file_size: int
file_hash: str
sender_id: str
receiver_id: str
status: TransferStatus = TransferStatus.PENDING
progress: float = 0.0
start_time: datetime = field(default_factory=datetime.now)
end_time: Optional[datetime] = None
def to_dict(self) -> dict:
"""转换为字典"""
return {
"transfer_id": self.transfer_id,
"file_name": self.file_name,
"file_size": self.file_size,
"file_hash": self.file_hash,
"sender_id": self.sender_id,
"receiver_id": self.receiver_id,
"status": self.status.value,
"progress": self.progress,
"start_time": self.start_time.isoformat(),
"end_time": self.end_time.isoformat() if self.end_time else None,
}
@classmethod
def from_dict(cls, data: dict) -> "FileTransferRecord":
"""从字典创建FileTransferRecord对象"""
end_time = None
if data.get("end_time"):
end_time = datetime.fromisoformat(data["end_time"])
return cls(
transfer_id=data["transfer_id"],
file_name=data["file_name"],
file_size=data["file_size"],
file_hash=data["file_hash"],
sender_id=data["sender_id"],
receiver_id=data["receiver_id"],
status=TransferStatus(data.get("status", "pending")),
progress=data.get("progress", 0.0),
start_time=datetime.fromisoformat(data["start_time"]),
end_time=end_time,
)
@dataclass
class PeerInfo:
"""对等端信息(用于局域网发现)"""
peer_id: str
username: str
ip_address: str
port: int
discovered_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> dict:
"""转换为字典"""
return {
"peer_id": self.peer_id,
"username": self.username,
"ip_address": self.ip_address,
"port": self.port,
"discovered_at": self.discovered_at.isoformat(),
}
@classmethod
def from_dict(cls, data: dict) -> "PeerInfo":
"""从字典创建PeerInfo对象"""
return cls(
peer_id=data["peer_id"],
username=data["username"],
ip_address=data["ip_address"],
port=data["port"],
discovered_at=datetime.fromisoformat(data["discovered_at"]),
)