Skip to content

Commit d57bd8a

Browse files
committed
Added SimpleCOTEvent, COTEvent classes and cot2xml function.
1 parent c2a7f38 commit d57bd8a

File tree

7 files changed

+365
-29
lines changed

7 files changed

+365
-29
lines changed

pytak/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
"""Python Team Awareness Kit (PyTAK) Module."""
1919

20-
__version__ = "7.0.2"
20+
__version__ = "7.1.0"
2121

2222
from .constants import ( # NOQA
2323
LOG_LEVEL,
@@ -57,16 +57,19 @@
5757
RXWorker,
5858
QueueWorker,
5959
CLITool,
60+
SimpleCOTEvent,
61+
COTEvent,
6062
)
6163

62-
from .functions import (
64+
from .functions import ( # NOQA
6365
split_host,
6466
parse_url,
6567
hello_event,
6668
cot_time,
6769
gen_cot,
6870
gen_cot_xml,
69-
) # NOQA
71+
cot2xml,
72+
)
7073

7174
from .client_functions import ( # NOQA
7275
create_udp_client,

pytak/classes.py

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818
"""PyTAK Class Definitions."""
1919

20+
import abc
2021
import asyncio
21-
import importlib.util
2222
import ipaddress
2323
import logging
2424
import multiprocessing as mp
@@ -27,19 +27,20 @@
2727

2828
import xml.etree.ElementTree as ET
2929

30-
from typing import Optional, Set, Union
30+
from dataclasses import dataclass
31+
from typing import Set, Union
3132

3233
from configparser import ConfigParser, SectionProxy
3334

3435
import pytak
3536

3637
try:
37-
import takproto
38+
import takproto # type: ignore
3839
except ImportError:
39-
pass
40+
takproto = None
4041

4142

42-
class Worker: # pylint: disable=too-few-public-methods
43+
class Worker:
4344
"""Meta class for all other Worker Classes."""
4445

4546
_logger = logging.getLogger(__name__)
@@ -74,17 +75,15 @@ def __init__(
7475

7576
tak_proto_version = int(self.config.get("TAK_PROTO") or pytak.DEFAULT_TAK_PROTO)
7677

77-
if tak_proto_version > 0 and importlib.util.find_spec("takproto") is None:
78+
if tak_proto_version > 0 and takproto is None:
7879
self._logger.warning(
7980
"TAK_PROTO is set to '%s', but the 'takproto' Python module is not installed.\n"
8081
"Try: python -m pip install pytak[with_takproto]\n"
8182
"See Also: https://pytak.rtfd.io/en/latest/compatibility/#tak-protocol-payload-version-1-protobuf",
8283
tak_proto_version,
8384
)
8485

85-
self.use_protobuf = tak_proto_version > 0 and importlib.util.find_spec(
86-
"takproto"
87-
)
86+
self.use_protobuf = tak_proto_version > 0 and takproto is not None
8887

8988
async def fts_compat(self) -> None:
9089
"""Apply FreeTAKServer (FTS) compatibility.
@@ -100,9 +99,10 @@ async def fts_compat(self) -> None:
10099
self._logger.debug("COMPAT: Sleeping for %ss", sleep_period)
101100
await asyncio.sleep(sleep_period)
102101

102+
@abc.abstractmethod
103103
async def handle_data(self, data: bytes) -> None:
104104
"""Handle data (placeholder method, please override)."""
105-
raise NotImplementedError("Subclasses need to override this method")
105+
pass
106106

107107
async def run(self, number_of_iterations=-1):
108108
"""Run this Thread, reads Data from Queue & passes data to next Handler."""
@@ -131,7 +131,7 @@ async def run(self, number_of_iterations=-1):
131131
number_of_iterations -= 1
132132

133133

134-
class TXWorker(Worker): # pylint: disable=too-few-public-methods
134+
class TXWorker(Worker):
135135
"""Works data queue and hands off to Protocol Workers.
136136
137137
You should create an TXWorker Instance using the `pytak.txworker_factory()`
@@ -190,7 +190,7 @@ async def send_data(self, data: bytes) -> None:
190190
self.writer.flush()
191191

192192

193-
class RXWorker(Worker): # pylint: disable=too-few-public-methods
193+
class RXWorker(Worker):
194194
"""Async receive (input) queue worker.
195195
196196
Reads events from a `pytak.protocol_factory()` reader and adds them to
@@ -212,6 +212,11 @@ def __init__(
212212
self.reader: asyncio.Protocol = reader
213213
self.reader_queue = None
214214

215+
@abc.abstractmethod
216+
async def handle_data(self, data: bytes) -> None:
217+
"""Handle data (placeholder method, please override)."""
218+
pass
219+
215220
async def readcot(self):
216221
"""Read CoT from the wire until we hit an event boundary."""
217222
try:
@@ -241,7 +246,7 @@ async def run(self, number_of_iterations=-1) -> None:
241246
self.queue.put_nowait(data)
242247

243248

244-
class QueueWorker(Worker): # pylint: disable=too-few-public-methods
249+
class QueueWorker(Worker):
245250
"""Read non-CoT Messages from an async network client.
246251
247252
(`asyncio.Protocol` or similar async network client)
@@ -263,6 +268,11 @@ def __init__(
263268
super().__init__(queue, config)
264269
self._logger.info("Using COT_URL='%s'", self.config.get("COT_URL"))
265270

271+
@abc.abstractmethod
272+
async def handle_data(self, data: bytes) -> None:
273+
"""Handle data (placeholder method, please override)."""
274+
pass
275+
266276
async def put_queue(
267277
self, data: bytes, queue_arg: Union[asyncio.Queue, mp.Queue, None] = None
268278
) -> None:
@@ -408,3 +418,63 @@ async def run(self):
408418

409419
for task in done:
410420
self._logger.info("Complete: %s", task)
421+
422+
423+
@dataclass
424+
class SimpleCOTEvent:
425+
"""CoT Event Dataclass."""
426+
427+
lat: Union[bytes, str, float, None] = None
428+
lon: Union[bytes, str, float, None] = None
429+
uid: Union[str, None] = None
430+
stale: Union[float, int, None] = None
431+
cot_type: Union[str, None] = None
432+
433+
def __str__(self) -> str:
434+
"""Return a formatted string representation of the dataclass."""
435+
event = self.to_xml()
436+
return ET.tostring(event, encoding="unicode")
437+
438+
def to_bytes(self) -> bytes:
439+
"""Return the class as bytes."""
440+
event = self.to_xml()
441+
return ET.tostring(event, encoding="utf-8")
442+
443+
def to_xml(self) -> ET.Element:
444+
"""Return a CoT Event as an XML string."""
445+
cotevent = COTEvent(
446+
lat=self.lat,
447+
lon=self.lon,
448+
uid=self.uid,
449+
stale=self.stale,
450+
cot_type=self.cot_type,
451+
le=pytak.DEFAULT_COT_VAL,
452+
ce=pytak.DEFAULT_COT_VAL,
453+
hae=pytak.DEFAULT_COT_VAL,
454+
)
455+
event = pytak.cot2xml(cotevent)
456+
return event
457+
458+
459+
@dataclass
460+
class COTEvent(SimpleCOTEvent):
461+
"""COT Event Dataclass."""
462+
463+
ce: Union[bytes, str, float, int, None] = None
464+
hae: Union[bytes, str, float, int, None] = None
465+
le: Union[bytes, str, float, int, None] = None
466+
467+
def to_xml(self) -> ET.Element:
468+
"""Return a CoT Event as an XML string."""
469+
cotevent = COTEvent(
470+
lat=self.lat,
471+
lon=self.lon,
472+
uid=self.uid,
473+
stale=self.stale,
474+
cot_type=self.cot_type,
475+
le=self.le,
476+
ce=self.ce,
477+
hae=self.hae,
478+
)
479+
event = pytak.cot2xml(cotevent)
480+
return event

pytak/crypto_functions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
USE_CRYPTOGRAPHY = True
3939
except ImportError as exc:
40-
warnings.warn(exc)
40+
warnings.warn(str(exc))
4141

4242

4343
def save_pem(pem: bytes, dest: Union[str, None] = None) -> str:
@@ -60,7 +60,7 @@ def load_cert(
6060
): # -> Set[_RSAPrivateKey, Certificate, Certificate]:
6161
"""Load RSA Keys & Certs from a pkcs12 ().p12) file."""
6262
if not USE_CRYPTOGRAPHY:
63-
raise Exception(INSTALL_MSG)
63+
raise ValueError(INSTALL_MSG)
6464

6565
with open(cert_path, "br+") as cp_fd:
6666
p12_data = cp_fd.read()
@@ -73,7 +73,7 @@ def load_cert(
7373
def convert_cert(cert_path: str, cert_pass: str) -> dict:
7474
"""Convert a P12 cert to PEM."""
7575
if not USE_CRYPTOGRAPHY:
76-
raise Exception(INSTALL_MSG)
76+
raise ValueError(INSTALL_MSG)
7777

7878
cert_paths = {
7979
"pk_pem_path": None,

pytak/functions.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,24 +160,65 @@ def connectString2url(conn_str: str) -> str: # pylint: disable=invalid-name
160160
return f"{uri_parts[2]}://{uri_parts[0]}:{uri_parts[1]}"
161161

162162

163+
def cot2xml(event: pytak.COTEvent) -> ET.Element:
164+
"""Generate a minimum COT Event as an XML object."""
165+
lat = str(event.lat or "0.0")
166+
lon = str(event.lon or "0.0")
167+
uid = event.uid or pytak.DEFAULT_HOST_ID
168+
stale = int(event.stale or pytak.DEFAULT_COT_STALE)
169+
cot_type = event.cot_type or "a-u-G"
170+
le = str(event.le or pytak.DEFAULT_COT_VAL)
171+
hae = str(event.hae or pytak.DEFAULT_COT_VAL)
172+
ce = str(event.ce or pytak.DEFAULT_COT_VAL)
173+
174+
xevent = ET.Element("event")
175+
xevent.set("version", "2.0")
176+
xevent.set("type", cot_type)
177+
xevent.set("uid", uid)
178+
xevent.set("how", "m-g")
179+
xevent.set("time", pytak.cot_time())
180+
xevent.set("start", pytak.cot_time())
181+
xevent.set("stale", pytak.cot_time(stale))
182+
183+
point = ET.Element("point")
184+
point.set("lat", lat)
185+
point.set("lon", lon)
186+
point.set("le", le)
187+
point.set("hae", hae)
188+
point.set("ce", ce)
189+
190+
flow_tags = ET.Element("_flow-tags_")
191+
_ft_tag: str = f"{pytak.DEFAULT_HOST_ID}-v{pytak.__version__}".replace("@", "-")
192+
flow_tags.set(_ft_tag, pytak.cot_time())
193+
194+
detail = ET.Element("detail")
195+
detail.append(flow_tags)
196+
197+
xevent.append(point)
198+
xevent.append(detail)
199+
200+
return xevent
201+
202+
163203
def gen_cot_xml(
164204
lat: Union[bytes, str, float, None] = None,
165205
lon: Union[bytes, str, float, None] = None,
166206
ce: Union[bytes, str, float, int, None] = None,
167207
hae: Union[bytes, str, float, int, None] = None,
168208
le: Union[bytes, str, float, int, None] = None,
169-
uid: Union[bytes, str, None] = None,
209+
uid: Union[str, None] = None,
170210
stale: Union[float, int, None] = None,
171-
cot_type: Union[bytes, str, None] = None,
211+
cot_type: Union[str, None] = None,
172212
) -> Optional[ET.Element]:
173213
"""Generate a minimum CoT Event as an XML object."""
214+
174215
lat = str(lat or "0.0")
175216
lon = str(lon or "0.0")
176217
ce = str(ce or pytak.DEFAULT_COT_VAL)
177218
hae = str(hae or pytak.DEFAULT_COT_VAL)
178219
le = str(le or pytak.DEFAULT_COT_VAL)
179220
uid = uid or pytak.DEFAULT_HOST_ID
180-
stale = stale or pytak.DEFAULT_COT_STALE
221+
stale = int(stale or pytak.DEFAULT_COT_STALE)
181222
cot_type = cot_type or "a-u-G"
182223

183224
event = ET.Element("event")

0 commit comments

Comments
 (0)