Этот коммит содержится в:
Alexander UR6LKW
2025-05-23 16:07:52 +03:00
родитель 70ed9938b2
Коммит 9a34e930dd
8 изменённых файлов: 134 добавлений и 110 удалений
+9 -2
Просмотреть файл
@@ -72,7 +72,8 @@ and exposes http API/dashboard on `8000/tcp` (may be changed with `--web-port` c
- ✔️ unit call routing - ✔️ unit call routing
- ✔️ allow single peer id check - ✔️ allow single peer id check
- ✔️ TA support (DMRA packet) - ✔️ TA support (DMRA packet)
- 🥕 routing entity (1 timeslot == 1 routing entity) - 🥕 dmr internal burst structure decoding (to fix rf fields and get ambe)
- 🥕 routing (1 timeslot == 1 routing entity)
- 🥕 only one call per ts for peer (per routing entity) - 🥕 only one call per ts for peer (per routing entity)
- 🥕 apps unit call routing (routing entity for app) - 🥕 apps unit call routing (routing entity for app)
- 🥕 configuration - 🥕 configuration
@@ -80,7 +81,6 @@ and exposes http API/dashboard on `8000/tcp` (may be changed with `--web-port` c
- 🥕 resolve id to callsigns - 🥕 resolve id to callsigns
- 🥕 routing 2: group subscriptions - 🥕 routing 2: group subscriptions
- 🥕 data calls (messages) - 🥕 data calls (messages)
- 🥕 dmr internal burst structure decoding (to fix rf fields and get ambe)
- 🥕 ambe decode/encode - 🥕 ambe decode/encode
- 🥕 log voice calls as files - 🥕 log voice calls as files
- 🥕 TA support 2 (inside DMRD packets) - 🥕 TA support 2 (inside DMRD packets)
@@ -96,6 +96,13 @@ Digital Mobile Radio (DMR) Systems;
Part 1: DMR Air Interface (AI) protocol Part 1: DMR Air Interface (AI) protocol
https://www.etsi.org/deliver/etsi_ts/102300_102399/10236101/01.02.01_60/ts_10236101v010201p.pdf https://www.etsi.org/deliver/etsi_ts/102300_102399/10236101/01.02.01_60/ts_10236101v010201p.pdf
- ETSI TS 102 361-2 V2.3.1 (2016-02)
Electromagnetic compatibility
and Radio spectrum Matters (ERM);
Digital Mobile Radio (DMR) Systems;
Part 2: DMR voice and generic services and facilities
https://www.etsi.org/deliver/etsi_ts/102300_102399/10236102/02.03.01_60/ts_10236102v020301p.pdf
- MMDVMHost by Jonathan Naylor, G4KLX - MMDVMHost by Jonathan Naylor, G4KLX
https://github.com/g4klx/MMDVMHost https://github.com/g4klx/MMDVMHost
+2 -1
Просмотреть файл
@@ -1,5 +1,6 @@
from .auth import AllowAllPeerAuth, DenyAllPeerAuth, ListPeerAuth
from .dispatcher import Dispatcher from .dispatcher import Dispatcher
from .dmrmaster import DMRMaster
from .dmrproto import DMRPPacketFactory from .dmrproto import DMRPPacketFactory
from .network import IDatagramSender, IDatagramReceiver from .network import IDatagramSender, IDatagramReceiver
from .pphex import hexdump from .pphex import hexdump
from .auth import AllowAllPeerAuth, DenyAllPeerAuth, ListPeerAuth
Просмотреть файл
+2
Просмотреть файл
@@ -0,0 +1,2 @@
from .mmdvm_l1 import *
from .exceptions import *
+70
Просмотреть файл
@@ -0,0 +1,70 @@
from abc import ABC, abstractmethod
from typing import Any
from .exceptions import DMRPFieldOutOfRangeException
class DMRPFieldBase(ABC):
def __init__(self, name: str, offset: int, bytelen: int) -> None:
self.name, self.offset, self.bytelen = name, offset, bytelen
self.eoffset = offset + bytelen
self.typename = f"bytes{self.bytelen}"
def get(self, obj) -> bytes:
return bytes(obj._data[self.offset:self.eoffset])
def set(self, obj, value: bytes):
if not isinstance(value, bytes) or self.bytelen != len(value):
raise DMRPFieldOutOfRangeException(self.name, self.typename)
obj._data[self.offset:self.eoffset] = value
@abstractmethod
def __get__(self, obj, cls = None) -> Any:
pass
@abstractmethod
def __set__(self, obj, value: Any) -> None:
pass
class DMRPFieldBytes(DMRPFieldBase):
def __get__(self, obj, cls = None) -> bytes:
return self.get(obj)
def __set__(self, obj, value: bytes) -> None:
self.set(obj, value)
class DMRPFieldStr(DMRPFieldBase):
def __init__(self, name: str, offset: int, bytelen: int,
pad_with: bytes = b'\x20') -> None:
super().__init__(name, offset, bytelen)
self.typename = 'str'
self.pad_with = pad_with
def __get__(self, obj, cls = None) -> str:
return (self.get(obj).strip(b'\x20\x00')
.decode(encoding='ascii', errors='ignore'))
def __set__(self, obj, value: str) -> None:
bvalue = value.encode()
if len(bvalue) > self.bytelen:
bvalue = bvalue[:self.bytelen]
bvalue = bvalue.ljust(self.bytelen, self.pad_with)
self.set(obj, bvalue)
class DMRPFieldInt(DMRPFieldBase):
def __init__(self, name: str, offset: int, bytelen: int) -> None:
super().__init__(name, offset, bytelen)
self.typename = f"uint{str(bytelen * 8)}"
def __get__(self, obj, cls = None) -> int:
return int.from_bytes(self.get(obj), byteorder="big")
def __set__(self, obj, value: int) -> None:
if not (0 <= value < 1<<(self.bytelen * 8)):
raise DMRPFieldOutOfRangeException(self.name, self.typename)
self.set(obj, value.to_bytes(self.bytelen, byteorder="big"))
+25
Просмотреть файл
@@ -0,0 +1,25 @@
class DMRPFieldOutOfRangeException(Exception):
"""
Exception raised when a packet field value is out of the allowed or expected range.
Attributes:
field: The name or identifier of the problematic field.
typename: The expected type or range description for the field.
"""
def __init__(self, field, typename) -> None:
super().__init__(f"Field out of range: {field} must be {typename}")
class DMRPUnknownPacketTypeException(Exception):
"""
Exception raised when the packet factory cannot recognize the packet type
from the given input data.
"""
pass
class DMRPBadPacket(Exception):
"""
Exception raised when a packet is structurally invalid or corrupted.
"""
pass
+25 -106
Просмотреть файл
@@ -5,37 +5,12 @@ import random
from abc import ABC from abc import ABC
from hashlib import sha256 from hashlib import sha256
from typing import Type, Self, Any from typing import Type, Self
from .base_fields import DMRPFieldInt, DMRPFieldBytes, DMRPFieldStr
############################# from .exceptions import DMRPBadPacket
# Local exception classes from .exceptions import DMRPFieldOutOfRangeException
############################# from .exceptions import DMRPUnknownPacketTypeException
class DMRPFieldOutOfRangeException(Exception):
"""
Exception raised when a packet field value is out of the allowed or expected range.
Attributes:
field: The name or identifier of the problematic field.
typename: The expected type or range description for the field.
"""
def __init__(self, field, typename) -> None:
super().__init__(f"Field out of range: {field} must be {typename}")
class DMRPUnknownPacketTypeException(Exception):
"""
Exception raised when the packet factory cannot recognize the packet type
from the given input data.
"""
pass
class DMRPBadPacket(Exception):
"""
Exception raised when a packet is structurally invalid or corrupted.
"""
pass
############################# #############################
@@ -45,81 +20,6 @@ def calc_password_hash(salt: bytes, password: str) -> bytes:
return sha256(salt + password.encode()).digest() return sha256(salt + password.encode()).digest()
#############################
# Field classes
#############################
class DMRPFieldBase(ABC):
def __init__(self, name: str, offset: int, bytelen: int) -> None:
self.name, self.offset, self.bytelen = name, offset, bytelen
self.eoffset = offset + bytelen
self.typename = f"bytes{self.bytelen}"
def __get__(self, obj, cls = None) -> Any:
return bytes(obj._data[self.offset:self.eoffset])
def __set__(self, obj, value: Any) -> None:
if not isinstance(value, bytes) or self.bytelen != len(value):
raise DMRPFieldOutOfRangeException(self.name, self.typename)
obj._data[self.offset:self.eoffset] = value
class DMRPFieldBytes(DMRPFieldBase):
def __get__(self, obj, cls = None) -> bytes:
return super().__get__(obj, cls)
def __set__(self, obj, value: bytes) -> None:
super().__set__(obj, value)
class DMRPFieldStr(DMRPFieldBase):
def __init__(self, name: str, offset: int, bytelen: int,
pad_with: bytes = b'\x20') -> None:
super().__init__(name, offset, bytelen)
self.typename = 'str'
self.pad_with = pad_with
def __get__(self, obj, cls = None) -> str:
return (super().__get__(obj, cls).strip(b'\x20\x00')
.decode(encoding='ascii', errors='ignore'))
def __set__(self, obj, value: str) -> None:
bvalue = value.encode()
if len(bvalue) > self.bytelen:
bvalue = bvalue[:self.bytelen]
bvalue = bvalue.ljust(self.bytelen, self.pad_with)
super().__set__(obj, bvalue)
class DMRPFieldInt(DMRPFieldBase):
def __init__(self, name: str, offset: int, bytelen: int) -> None:
super().__init__(name, offset, bytelen)
self.typename = f"uint{str(bytelen * 8)}"
def __get__(self, obj, cls = None) -> int:
return int.from_bytes(super().__get__(obj, cls), byteorder="big")
def __set__(self, obj, value: int) -> None:
if not (0 <= value < 1<<(self.bytelen * 8)):
raise DMRPFieldOutOfRangeException(self.name, self.typename)
super().__set__(obj, value.to_bytes(self.bytelen, byteorder="big"))
class DMRPFieldPeerAuto(DMRPFieldInt):
def fixoffset(self, offset: int) -> None:
self.offset, self.eoffset = offset, offset + self.bytelen
def __init__(self, name: str) -> None:
super().__init__(name, 4, 4)
def __get__(self, obj, cls = None) -> int:
self.fixoffset(len(obj.PKT_TYPE))
return super().__get__(obj, cls)
def __set__(self, obj, value: int) -> None:
self.fixoffset(len(obj.PKT_TYPE))
super().__set__(obj, value)
############################# #############################
# Packet classes hierarchy # Packet classes hierarchy
############################# #############################
@@ -206,6 +106,25 @@ class DMRPBasePeerPacket(DMRPBasePacket, ABC):
Abstract base class (more specific), which handles typical packets with Abstract base class (more specific), which handles typical packets with
type header and peer_id field type header and peer_id field
""" """
class DMRPFieldPeerAuto(DMRPFieldInt):
"""
Int field, which autodetects header length (PKT_TYPE) and
offsets peer_id for its len
"""
def fixoffset(self, offset: int) -> None:
self.offset, self.eoffset = offset, offset + self.bytelen
def __init__(self, name: str) -> None:
super().__init__(name, 4, 4)
def __get__(self, obj, cls = None) -> int:
self.fixoffset(len(obj.PKT_TYPE))
return super().__get__(obj, cls)
def __set__(self, obj, value: int) -> None:
self.fixoffset(len(obj.PKT_TYPE))
super().__set__(obj, value)
# peer_id # peer_id
peer_id: DMRPFieldInt = DMRPFieldPeerAuto('peer_id') peer_id: DMRPFieldInt = DMRPFieldPeerAuto('peer_id')
@@ -494,7 +413,7 @@ class DMRPPacketFactory:
and user-registered custom packet types. and user-registered custom packet types.
""" """
__instance: Self|None = None __instance: DMRPPacketFactory|None = None
@classmethod @classmethod
def fd(cls, data: bytes) -> DMRPBasePacket: def fd(cls, data: bytes) -> DMRPBasePacket:
+1 -1
Просмотреть файл
@@ -1,7 +1,7 @@
import logging import logging
import time import time
from dmrmaster import DMRMaster from dmrtools import DMRMaster
from dmrtools import AllowAllPeerAuth, ListPeerAuth from dmrtools import AllowAllPeerAuth, ListPeerAuth
from dmrtools.parrot_app import ParrotApp from dmrtools.parrot_app import ParrotApp