Skip to content
18 changes: 7 additions & 11 deletions opendis/dis7.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
enum8,
enum16,
enum32,
int8,
int16,
int32,
uint8,
uint16,
Expand All @@ -19,6 +17,7 @@
struct16,
struct32,
)
from .record import SpreadSpectrum


class DataQueryDatumSpecification:
Expand Down Expand Up @@ -2025,11 +2024,11 @@ class ModulationType:
"""

def __init__(self,
spreadSpectrum: struct16 = 0, # See RPR Enumerations
spreadSpectrum: SpreadSpectrum | None = None, # See RPR Enumerations
majorModulation: enum16 = 0, # [UID 155]
detail: enum16 =0, # [UID 156-162]
radioSystem: enum16 =0): # [UID 163]
self.spreadSpectrum = spreadSpectrum
detail: enum16 = 0, # [UID 156-162]
radioSystem: enum16 = 0): # [UID 163]
self.spreadSpectrum = spreadSpectrum or SpreadSpectrum()
"""This field shall indicate the spread spectrum technique or combination of spread spectrum techniques in use. Bit field. 0=freq hopping, 1=psuedo noise, time hopping=2, reamining bits unused"""
self.majorModulation = majorModulation
"""the major classification of the modulation type."""
Expand Down Expand Up @@ -5477,7 +5476,7 @@ def __init__(self,
antennaPatternList=None):
super(TransmitterPdu, self).__init__()
self.radioReferenceID = radioReferenceID or EntityID()
"""ID of the entitythat is the source of the communication"""
"""ID of the entity that is the source of the communication"""
self.radioNumber = radioNumber
"""particular radio within an entity"""
self.radioEntityType = radioEntityType or EntityType() # TODO: validation
Expand All @@ -5491,18 +5490,15 @@ def __init__(self,
self.frequency = frequency
self.transmitFrequencyBandwidth = transmitFrequencyBandwidth
self.power = power
"""transmission power"""
self.modulationType = modulationType or ModulationType()
self.cryptoSystem = cryptoSystem
self.cryptoKeyId = cryptoKeyId
# FIXME: Refactpr modulation parameters into its own record class
# FIXME: Refactor modulation parameters into its own record class
self.modulationParameterCount = modulationParameterCount
self.padding2 = 0
self.padding3 = 0
self.modulationParametersList = modulationParametersList or []
"""variable length list of modulation parameters"""
self.antennaPatternList = antennaPatternList or []
"""variable length list of antenna pattern records"""
# TODO: zero or more Variable Transmitter Parameters records (see 6.2.95)

@property
Expand Down
149 changes: 149 additions & 0 deletions opendis/record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""Record type classes for OpenDIS7.

This module defines classes for various record types used in DIS PDUs.
"""

from collections.abc import Sequence
from ctypes import _SimpleCData, BigEndianStructure, c_uint
from .types import (
bf_enum,
bf_int,
bf_uint,
)

from .DataInputStream import DataInputStream
from .DataOutputStream import DataOutputStream


def _bitfield(
name: str,
bytesize: int,
fields: Sequence[
tuple[str, type[_SimpleCData]] | tuple[str, type[_SimpleCData], int]
],
):
"""Factory function for bitfield structs, which are subclasses of
ctypes.Structure.
These are used in records that require them to unpack non-octet-sized fields.

Args:
name: Name of the bitfield struct.
bytesize: Size of the bitfield in bytes.
fields: Sequence of tuples defining the fields of the bitfield.
See https://docs.python.org/3/library/ctypes.html#ctypes.Structure._fields_
"""
if bytesize <= 0:
raise ValueError("Cannot create bitfield with less than one byte")

class Bitfield(BigEndianStructure):
_fields_ = fields

@staticmethod
def marshalledSize() -> int:
return bytesize

def serialize(self, outputStream: DataOutputStream) -> None:
outputStream.write_bytes(bytes(self))

@classmethod
def parse(cls, inputStream: DataInputStream) -> "Bitfield":
return cls.from_buffer_copy(inputStream.read_bytes(bytesize))
Bitfield.__name__ = name
return Bitfield


class NetId:
"""Annex C, Table C.5

Represents an Operational Net in the format of NXX.XYY, where:
N = Mode
XXX = Net Number
YY = Frequency Table
"""

_struct = _bitfield(name="NetId", bytesize=2, fields=[
("netNumber", c_uint, 10),
("frequencyTable", c_uint, 2),
("mode", c_uint, 2),
("padding", c_uint, 2)
])

def __init__(self,
netNumber: bf_uint = 0,
frequencyTable: bf_enum = 0, # [UID 299]
mode: bf_enum = 0, # [UID 298]
padding: bf_uint = 0):
# Net number ranging from 0 to 999 decimal
self.netNumber = netNumber
self.frequencyTable = frequencyTable
self.mode = mode
self.padding = padding

def marshalledSize(self) -> int:
return self._struct.marshalledSize()

def serialize(self, outputStream: DataOutputStream) -> None:
self._struct(
self.netNumber,
self.frequencyTable,
self.mode,
self.padding
).serialize(outputStream)

def parse(self, inputStream: DataInputStream) -> None:
record_bitfield = self._struct.parse(inputStream)
self.netNumber = record_bitfield.netNumber
self.frequencyTable = record_bitfield.frequencyTable
self.mode = record_bitfield.mode
self.padding = record_bitfield.padding


class SpreadSpectrum:
"""6.2.59 Modulation Type Record, Table 90

Modulation used for radio transmission is characterized in a generic
fashion by the Spread Spectrum, Major Modulation, and Detail fields.

Each independent type of spread spectrum technique shall be represented by
a single element of this array.
If a particular spread spectrum technique is in use, the corresponding array
element shall be set to one; otherwise it shall be set to zero.
All unused array elements shall be set to zero.

In Python, the presence or absence of each technique is indicated by a bool.
"""

_struct = _bitfield("SpreadSpectrum", 2, [
("frequencyHopping", c_uint, 1),
("pseudoNoise", c_uint, 1),
("timeHopping", c_uint, 1),
("padding", c_uint, 13)
])

def __init__(self,
frequencyHopping: bool = False,
pseudoNoise: bool = False,
timeHopping: bool = False,
padding: bf_uint = 0):
self.frequencyHopping = frequencyHopping
self.pseudoNoise = pseudoNoise
self.timeHopping = timeHopping
self.padding = padding

def marshalledSize(self) -> int:
return self._struct.marshalledSize()

def serialize(self, outputStream: DataOutputStream) -> None:
# Bitfield expects int input
self._struct(
int(self.frequencyHopping),
int(self.pseudoNoise),
int(self.timeHopping),
self.padding
).serialize(outputStream)

def parse(self, inputStream: DataInputStream) -> None:
record_bitfield = self._struct.parse(inputStream)
self.frequencyHopping = bool(record_bitfield.frequencyHopping)
self.pseudoNoise = bool(record_bitfield.pseudoNoise)
self.timeHopping = bool(record_bitfield.timeHopping)
5 changes: 5 additions & 0 deletions opendis/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@
struct32 = int
char8 = str
char16 = str

# Non-octet-size types for bitfields
bf_enum = int
bf_int = int
bf_uint = int