1+ import socket
2+ import ssl
3+ from cryptography .hazmat .primitives .serialization import Encoding , PrivateFormat , NoEncryption
4+ from cryptography .hazmat .primitives .serialization .pkcs12 import load_key_and_certificates
5+ import asyncio
6+ import websockets
7+ import json
8+ import datetime
9+ import CoT
10+
11+
12+ # Load the .p12 file
13+ p12_file_path = 'examples/admin.p12'
14+ p12_password = b'atakatak'
15+
16+ with open (p12_file_path , 'rb' ) as f :
17+ p12_data = f .read ()
18+
19+ private_key , certificate , additional_certificates = load_key_and_certificates (p12_data , p12_password )
20+
21+ # Save cert and key to temp files
22+ with open ('client_cert.pem' , 'wb' ) as cert_file :
23+ cert_file .write (certificate .public_bytes (Encoding .PEM ))
24+
25+ with open ('client_key.pem' , 'wb' ) as key_file :
26+ key_file .write (private_key .private_bytes (Encoding .PEM , PrivateFormat .TraditionalOpenSSL , NoEncryption ()))
27+
28+ # Insecure context
29+ context = ssl ._create_unverified_context ()
30+ context .load_cert_chain (certfile = 'client_cert.pem' , keyfile = 'client_key.pem' )
31+
32+ # Connect to server
33+ hostname = 'x.x.x.x'
34+ port = 8089
35+
36+ def get_sar (mmsi : str ) -> bool :
37+ """Get the AIS Search-And-Rescue (SAR) status of a given MMSI.
38+
39+ Search and Rescue Aircraft:
40+ AIS and DSC equipment used on search and rescue aircraft use the format
41+ 111213M4I5D6X7X8X9 where the digits 4, 5 and 6 represent the MID and X
42+ is any figure from 0 to 9. In the United States, these MMSIs are
43+ currently only used by the U.S. Coast Guard.
44+ Src: https://www.navcen.uscg.gov/?pageName=mtmmsi
45+
46+ :param mmsi: str MMSI as decoded from AIS data.
47+ :return:
48+ """
49+ sar = False
50+ _mmsi = str (mmsi )
51+ if _mmsi [:3 ] == "111" :
52+ sar = True
53+ elif _mmsi [:5 ] in ["30386" , "33885" ]: # US Coast Guard
54+ sar = True
55+ return sar
56+
57+ def create_cot_event (message : dict ):
58+ now = datetime .datetime .now (datetime .timezone .utc )
59+ stale = now + datetime .timedelta (minutes = 2 )
60+
61+ print (message ['Message' ]['PositionReport' ])
62+ print (message )
63+
64+ return CoT .Event (
65+ version = "2.0" ,
66+ type = "a-u-S-X-M" ,
67+ access = "Undefined" ,
68+ uid = "MMSI-{}" .format (message ['MetaData' ]['MMSI' ]),
69+ time = now ,
70+ start = now ,
71+ stale = stale ,
72+ how = "m-f" ,
73+ qos = "2-i-c" ,
74+ point = CoT .Point (lat = float (message ["Message" ]["PositionReport" ]["Latitude" ]), lon = float (message ["Message" ]["PositionReport" ]["Longitude" ]), hae = 9999999 , ce = 9999999 , le = 9999999 ),
75+ detail = {"contact" : {"callsign" : message ["MetaData" ]["ShipName" ]}},
76+ )
77+
78+ async def connect_ais_stream ():
79+ async with websockets .connect ("wss://stream.aisstream.io/v0/stream" ) as websocket :
80+ subscribe_message = {
81+ "APIKey" : "" ,
82+ "BoundingBoxes" : [[[- 90 , - 180 ], [90 , 180 ]]],
83+ "FilteringMMSI" : ["368207620" , "367719770" , "211476060" ],
84+ "FilderingMessageTypes" : ["PositionReport" ]
85+ }
86+ subscribe_message_json = json .dumps (subscribe_message )
87+ await websocket .send (subscribe_message_json )
88+
89+ async for message_json in websocket :
90+ message = json .loads (message_json )
91+ message_type = message ["MessageType" ]
92+ if message_type == "PositionReport" :
93+ ais_message = message ['Message' ]['PositionReport' ]
94+ cot_event = create_cot_event (message )
95+ data = cot_event .xml ()
96+ b = bytes (data , encoding = "utf-8" )
97+
98+ with socket .create_connection ((hostname , port )) as sock :
99+ with context .wrap_socket (sock , server_hostname = hostname ) as ssock :
100+ ssock .sendall (b )
101+ data = ssock .recv (1024 )
102+
103+
104+ def main ():
105+ asyncio .run (asyncio .run (connect_ais_stream ()))
106+
107+ if __name__ == "__main__" :
108+ main ()
0 commit comments