dcpmessage 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dcpmessage-1.2.2/PKG-INFO +69 -0
- dcpmessage-1.2.2/README.md +61 -0
- dcpmessage-1.2.2/dcpmessage/__init__.py +1 -0
- dcpmessage-1.2.2/dcpmessage/credentials.py +139 -0
- dcpmessage-1.2.2/dcpmessage/dcp_message.py +118 -0
- dcpmessage-1.2.2/dcpmessage/exceptions.py +152 -0
- dcpmessage-1.2.2/dcpmessage/ldds_client.py +240 -0
- dcpmessage-1.2.2/dcpmessage/ldds_message.py +232 -0
- dcpmessage-1.2.2/dcpmessage/logs.py +9 -0
- dcpmessage-1.2.2/dcpmessage/search_criteria.py +207 -0
- dcpmessage-1.2.2/dcpmessage/utils.py +29 -0
- dcpmessage-1.2.2/dcpmessage.egg-info/PKG-INFO +69 -0
- dcpmessage-1.2.2/dcpmessage.egg-info/SOURCES.txt +22 -0
- dcpmessage-1.2.2/dcpmessage.egg-info/dependency_links.txt +1 -0
- dcpmessage-1.2.2/dcpmessage.egg-info/top_level.txt +1 -0
- dcpmessage-1.2.2/pyproject.toml +20 -0
- dcpmessage-1.2.2/setup.cfg +4 -0
- dcpmessage-1.2.2/tests/test_credentials.py +48 -0
- dcpmessage-1.2.2/tests/test_dcp_message.py +27 -0
- dcpmessage-1.2.2/tests/test_ldds_client.py +15 -0
- dcpmessage-1.2.2/tests/test_ldds_message.py +43 -0
- dcpmessage-1.2.2/tests/test_search_criteria.py +39 -0
- dcpmessage-1.2.2/tests/test_server_exceptions.py +26 -0
- dcpmessage-1.2.2/tests/test_utils.py +15 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dcpmessage
|
|
3
|
+
Version: 1.2.2
|
|
4
|
+
Summary: package for retrieving GOES DCS message data from LRGS servers.
|
|
5
|
+
Author: dcpmessage authors
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# Introduction
|
|
10
|
+
|
|
11
|
+
The `dcpmessage` package is a Python library designed for retrieving GOES DCS message data from LRGS servers. Initially
|
|
12
|
+
developed for deployment as an AWS Lambda function, its primary purpose is to execute periodic data retrieval for
|
|
13
|
+
specified Data Collection Platforms (DCPs). The decoding, processing, or archiving the received DCP messages should
|
|
14
|
+
be handled by other processes as this tool is intended only for retrieving the messages.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Download the latest `.tar.gz` from [releases page](https://github.com/dcspy/dcspy/releases) and install it using `pip`
|
|
19
|
+
|
|
20
|
+
```shell
|
|
21
|
+
pip install dcpmessage-#.#.#.tar.gz
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from dcpmessage import DcpMessage
|
|
28
|
+
|
|
29
|
+
msg = DcpMessage.get(username="<USERNAME>",
|
|
30
|
+
password="<PASSWORD>",
|
|
31
|
+
search_criteria="<PATH TO SEARCH CRITERIA>",
|
|
32
|
+
host="<HOST>",
|
|
33
|
+
)
|
|
34
|
+
print("\n".join(msg))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Search Criteria
|
|
38
|
+
|
|
39
|
+
Path to Search Criteria file should be passed when getting dcp messages. Search Criteria file should be `json`. An
|
|
40
|
+
example is provided below.
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"DRS_SINCE": "now - 1 hour",
|
|
45
|
+
"DRS_UNTIL": "now",
|
|
46
|
+
"SOURCE": [
|
|
47
|
+
"GOES_SELFTIMED",
|
|
48
|
+
"GOES_RANDOM"
|
|
49
|
+
],
|
|
50
|
+
"DCP_ADDRESS": [
|
|
51
|
+
"address1",
|
|
52
|
+
"address2"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- NOTE THAT, only following keywords are supported by `dcpmessage` at this point:
|
|
58
|
+
- `DRS_SINCE`: string
|
|
59
|
+
- `DRS_UNTIL`: string
|
|
60
|
+
- `SOURCE` (can be `GOES_SELFTIMED` or `GOES_RANDOM`, or both) : list of strings
|
|
61
|
+
- `DCP_ADDRESS` (can add multiple dcp addresses): list of strings
|
|
62
|
+
- All other keywords will be ignored.
|
|
63
|
+
- For more information about search criteria, check [opendcs
|
|
64
|
+
docs](https://opendcs-env.readthedocs.io/en/stable/legacy-lrgs-userguide.html#search-criteria-file-format).
|
|
65
|
+
|
|
66
|
+
## Contributors
|
|
67
|
+
|
|
68
|
+
- [Manoj Kotteda](https://github.com/orgs/dcspy/people/manojkotteda)
|
|
69
|
+
- Darshan Baral
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Introduction
|
|
2
|
+
|
|
3
|
+
The `dcpmessage` package is a Python library designed for retrieving GOES DCS message data from LRGS servers. Initially
|
|
4
|
+
developed for deployment as an AWS Lambda function, its primary purpose is to execute periodic data retrieval for
|
|
5
|
+
specified Data Collection Platforms (DCPs). The decoding, processing, or archiving the received DCP messages should
|
|
6
|
+
be handled by other processes as this tool is intended only for retrieving the messages.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Download the latest `.tar.gz` from [releases page](https://github.com/dcspy/dcspy/releases) and install it using `pip`
|
|
11
|
+
|
|
12
|
+
```shell
|
|
13
|
+
pip install dcpmessage-#.#.#.tar.gz
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from dcpmessage import DcpMessage
|
|
20
|
+
|
|
21
|
+
msg = DcpMessage.get(username="<USERNAME>",
|
|
22
|
+
password="<PASSWORD>",
|
|
23
|
+
search_criteria="<PATH TO SEARCH CRITERIA>",
|
|
24
|
+
host="<HOST>",
|
|
25
|
+
)
|
|
26
|
+
print("\n".join(msg))
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Search Criteria
|
|
30
|
+
|
|
31
|
+
Path to Search Criteria file should be passed when getting dcp messages. Search Criteria file should be `json`. An
|
|
32
|
+
example is provided below.
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"DRS_SINCE": "now - 1 hour",
|
|
37
|
+
"DRS_UNTIL": "now",
|
|
38
|
+
"SOURCE": [
|
|
39
|
+
"GOES_SELFTIMED",
|
|
40
|
+
"GOES_RANDOM"
|
|
41
|
+
],
|
|
42
|
+
"DCP_ADDRESS": [
|
|
43
|
+
"address1",
|
|
44
|
+
"address2"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- NOTE THAT, only following keywords are supported by `dcpmessage` at this point:
|
|
50
|
+
- `DRS_SINCE`: string
|
|
51
|
+
- `DRS_UNTIL`: string
|
|
52
|
+
- `SOURCE` (can be `GOES_SELFTIMED` or `GOES_RANDOM`, or both) : list of strings
|
|
53
|
+
- `DCP_ADDRESS` (can add multiple dcp addresses): list of strings
|
|
54
|
+
- All other keywords will be ignored.
|
|
55
|
+
- For more information about search criteria, check [opendcs
|
|
56
|
+
docs](https://opendcs-env.readthedocs.io/en/stable/legacy-lrgs-userguide.html#search-criteria-file-format).
|
|
57
|
+
|
|
58
|
+
## Contributors
|
|
59
|
+
|
|
60
|
+
- [Manoj Kotteda](https://github.com/orgs/dcspy/people/manojkotteda)
|
|
61
|
+
- Darshan Baral
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .dcp_message import DcpMessage
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from dcpmessage.utils import ByteUtil
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HashAlgo:
|
|
8
|
+
"""
|
|
9
|
+
A class representing a hashing algorithm.
|
|
10
|
+
|
|
11
|
+
This class serves as a base class for different hashing algorithms,
|
|
12
|
+
such as SHA-1 and SHA-256, providing a common interface to initialize
|
|
13
|
+
and create new hash objects.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
algorithm (str): The name of the hashing algorithm.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, algorithm):
|
|
20
|
+
"""
|
|
21
|
+
Initialize the HashAlgo with a specific hashing algorithm.
|
|
22
|
+
|
|
23
|
+
:param algorithm: The name of the hashing algorithm (e.g., "sha1", "sha256").
|
|
24
|
+
"""
|
|
25
|
+
assert algorithm in {"sha1", "sha256"}, (
|
|
26
|
+
f"{algorithm} is not a supported hash algorithm"
|
|
27
|
+
)
|
|
28
|
+
self.algorithm = algorithm
|
|
29
|
+
|
|
30
|
+
def new(self):
|
|
31
|
+
"""
|
|
32
|
+
Create a new hash object using the specified algorithm.
|
|
33
|
+
|
|
34
|
+
:return: A new hash object from the hashlib library.
|
|
35
|
+
"""
|
|
36
|
+
return hashlib.new(self.algorithm)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Sha1(HashAlgo):
|
|
40
|
+
"""
|
|
41
|
+
A class representing the SHA-1 hashing algorithm.
|
|
42
|
+
|
|
43
|
+
Inherits from the HashAlgo class and is pre-configured with the "sha1" algorithm.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
"""
|
|
48
|
+
Initialize the Sha1 class with the "sha1" algorithm.
|
|
49
|
+
"""
|
|
50
|
+
super().__init__("sha1")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Sha256(HashAlgo):
|
|
54
|
+
"""
|
|
55
|
+
A class representing the SHA-256 hashing algorithm.
|
|
56
|
+
|
|
57
|
+
Inherits from the HashAlgo class and is pre-configured with the "sha256" algorithm.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self):
|
|
61
|
+
"""
|
|
62
|
+
Initialize the Sha256 class with the "sha256" algorithm.
|
|
63
|
+
"""
|
|
64
|
+
super().__init__("sha256")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Credentials:
|
|
68
|
+
def __init__(self, username: str = None, password: str = None):
|
|
69
|
+
"""
|
|
70
|
+
Initialize the Credentials with a username and password.
|
|
71
|
+
|
|
72
|
+
:param username: The username of the user.
|
|
73
|
+
:param password: The password of the user.
|
|
74
|
+
"""
|
|
75
|
+
self.username = username
|
|
76
|
+
self.preliminary_hash = self.get_preliminary_hash(password)
|
|
77
|
+
|
|
78
|
+
def get_preliminary_hash(self, password: str) -> bytes:
|
|
79
|
+
"""
|
|
80
|
+
Generate the preliminary hash for the password.
|
|
81
|
+
|
|
82
|
+
This method creates a hash by combining the username and password multiple times.
|
|
83
|
+
|
|
84
|
+
:param password: The password to hash.
|
|
85
|
+
:return: The resulting hash as bytes.
|
|
86
|
+
"""
|
|
87
|
+
username_b = self.username.encode("utf-8")
|
|
88
|
+
password_b = password.encode("utf-8")
|
|
89
|
+
md = Sha1().new()
|
|
90
|
+
md.update(username_b)
|
|
91
|
+
md.update(password_b)
|
|
92
|
+
md.update(username_b)
|
|
93
|
+
md.update(password_b)
|
|
94
|
+
return md.digest()
|
|
95
|
+
|
|
96
|
+
def get_authenticator_hash(self, time: datetime, hash_algo: HashAlgo) -> str:
|
|
97
|
+
"""
|
|
98
|
+
Generate an authenticator hash using a specified hash algorithm.
|
|
99
|
+
|
|
100
|
+
This hash is used for authenticating the user based on the current time and the user's credentials.
|
|
101
|
+
|
|
102
|
+
:param time: The current time as a datetime object.
|
|
103
|
+
:param hash_algo: The hashing algorithm to use (e.g., Sha1, Sha256).
|
|
104
|
+
:return: The authenticator hash as a hexadecimal string.
|
|
105
|
+
"""
|
|
106
|
+
time_t = int(time.timestamp())
|
|
107
|
+
time_b = time_t.to_bytes(length=4, byteorder="big")
|
|
108
|
+
username_b = self.username.encode("utf-8")
|
|
109
|
+
|
|
110
|
+
"""Create an authenticator."""
|
|
111
|
+
md = hash_algo.new()
|
|
112
|
+
md.update(username_b)
|
|
113
|
+
md.update(self.preliminary_hash)
|
|
114
|
+
md.update(time_b)
|
|
115
|
+
md.update(username_b)
|
|
116
|
+
md.update(self.preliminary_hash)
|
|
117
|
+
md.update(time_b)
|
|
118
|
+
authenticator_bytes = md.digest()
|
|
119
|
+
return ByteUtil.to_hex_string(authenticator_bytes)
|
|
120
|
+
|
|
121
|
+
def get_authenticated_hello(self, time: datetime, hash_algo: HashAlgo):
|
|
122
|
+
"""
|
|
123
|
+
Create an authenticated hello message for the user.
|
|
124
|
+
|
|
125
|
+
This method combines the username, current time, authenticator hash, and protocol version
|
|
126
|
+
into a single string used for authentication with the server.
|
|
127
|
+
|
|
128
|
+
:param time: The current time as a datetime object.
|
|
129
|
+
:param hash_algo: The hashing algorithm to use for the authenticator hash (e.g., Sha1, Sha256).
|
|
130
|
+
:return: The authenticated hello message as a string.
|
|
131
|
+
"""
|
|
132
|
+
authenticator_hash = self.get_authenticator_hash(time, hash_algo)
|
|
133
|
+
time_str = time.strftime("%y%j%H%M%S")
|
|
134
|
+
protocol_version = 14
|
|
135
|
+
|
|
136
|
+
authenticated_hello = (
|
|
137
|
+
f"{self.username} {time_str} {authenticator_hash} {protocol_version}"
|
|
138
|
+
)
|
|
139
|
+
return authenticated_hello
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from .ldds_client import LddsClient
|
|
5
|
+
from .ldds_message import LddsMessage
|
|
6
|
+
from .logs import get_logger
|
|
7
|
+
from .search_criteria import SearchCriteria
|
|
8
|
+
|
|
9
|
+
logger = get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DcpMessage:
|
|
13
|
+
"""
|
|
14
|
+
Class for handling DCP messages, including fetching and processing them
|
|
15
|
+
from a remote server using the LDDS protocol.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
:param DATA_LENGTH: Standard length of the data field in a DCP message.
|
|
19
|
+
:param: HEADER_LENGTH: Standard length of the header in a DCP message.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
DATA_LENGTH = 32
|
|
23
|
+
HEADER_LENGTH = 37
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def get(
|
|
27
|
+
username: str,
|
|
28
|
+
password: str,
|
|
29
|
+
search_criteria: Union[dict, str, Path],
|
|
30
|
+
host: str,
|
|
31
|
+
port: int = 16003,
|
|
32
|
+
timeout: int = 30,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Fetches DCP messages from a server based on provided search criteria.
|
|
36
|
+
|
|
37
|
+
This method handles the complete process of connecting to the server,
|
|
38
|
+
authenticating, sending search criteria, retrieving DCP messages, and
|
|
39
|
+
finally disconnecting.
|
|
40
|
+
|
|
41
|
+
:param username: Username for server authentication.
|
|
42
|
+
:param password: Password for server authentication.
|
|
43
|
+
:param search_criteria: File path to search criteria or search criteria as a string.
|
|
44
|
+
:param host: Hostname or IP address of the server.
|
|
45
|
+
:param port: Port number for server connection (default: 16003).
|
|
46
|
+
:param timeout: Connection timeout in seconds (default: 30 seconds).
|
|
47
|
+
Will be passed to `socket.settimeout <https://docs.python.org/3/library/socket.html#socket.socket.settimeout>`_
|
|
48
|
+
:return: List of DCP messages retrieved from the server.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
client = LddsClient(host=host, port=port, timeout=timeout)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
client.connect()
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.error("Failed to connect to server.")
|
|
57
|
+
raise e
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
client.authenticate_user(username, password)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error("Failed to authenticate user.")
|
|
63
|
+
client.disconnect()
|
|
64
|
+
raise e
|
|
65
|
+
|
|
66
|
+
match search_criteria:
|
|
67
|
+
case str() | Path():
|
|
68
|
+
criteria = SearchCriteria.from_file(search_criteria)
|
|
69
|
+
case dict():
|
|
70
|
+
criteria = SearchCriteria.from_dict(search_criteria)
|
|
71
|
+
case _:
|
|
72
|
+
raise TypeError("search_criteria must be a filepath or a dict.")
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
client.send_search_criteria(criteria)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error("Failed to send search criteria.")
|
|
78
|
+
client.disconnect()
|
|
79
|
+
raise e
|
|
80
|
+
|
|
81
|
+
# Retrieve the DCP block and process it into individual messages
|
|
82
|
+
dcp_blocks = client.request_dcp_blocks()
|
|
83
|
+
dcp_messages = DcpMessage.explode(dcp_blocks)
|
|
84
|
+
|
|
85
|
+
client.send_goodbye()
|
|
86
|
+
client.disconnect()
|
|
87
|
+
return dcp_messages
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def explode(
|
|
91
|
+
message_blocks: list[LddsMessage],
|
|
92
|
+
) -> list[str]:
|
|
93
|
+
"""
|
|
94
|
+
Splits a message block bytes containing multiple DCP messages into individual messages.
|
|
95
|
+
|
|
96
|
+
:param message_blocks: message block (concatenated response from the server).
|
|
97
|
+
:return: A list of individual DCP messages.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
data_length = DcpMessage.DATA_LENGTH
|
|
101
|
+
header_length = DcpMessage.HEADER_LENGTH
|
|
102
|
+
dcp_messages = []
|
|
103
|
+
|
|
104
|
+
for ldds_message in message_blocks:
|
|
105
|
+
message = ldds_message.message_data.decode()
|
|
106
|
+
start_index = 0
|
|
107
|
+
while start_index < ldds_message.message_length:
|
|
108
|
+
# Extract the length of the current message
|
|
109
|
+
message_length = int(
|
|
110
|
+
message[(start_index + data_length) : (start_index + header_length)]
|
|
111
|
+
)
|
|
112
|
+
# Extract the entire message using the determined length
|
|
113
|
+
end_index = start_index + header_length + message_length
|
|
114
|
+
dcp_message = message[start_index:end_index]
|
|
115
|
+
dcp_messages.append(dcp_message)
|
|
116
|
+
start_index += DcpMessage.HEADER_LENGTH + message_length
|
|
117
|
+
|
|
118
|
+
return dcp_messages
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from enum import UNIQUE, Enum, verify
|
|
2
|
+
|
|
3
|
+
from .utils import ByteUtil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@verify(UNIQUE)
|
|
7
|
+
class ServerErrorCode(Enum):
|
|
8
|
+
DSUCCESS = 0, "Success."
|
|
9
|
+
DNOFLAG = 1, "Could not find start of message flag."
|
|
10
|
+
DDUMMY = 2, "Message found (and loaded) but it's a dummy."
|
|
11
|
+
DLONGLIST = 3, "Network list was too long to upload."
|
|
12
|
+
DARCERROR = 4, "Error reading archive file."
|
|
13
|
+
DNOCONFIG = 5, "Cannot attach to configuration shared memory"
|
|
14
|
+
DNOSRCHSHM = 6, "Cannot attach to search shared memory"
|
|
15
|
+
DNODIRLOCK = 7, "Could not get ID of directory lock semaphore"
|
|
16
|
+
DNODIRFILE = 8, "Could not open message directory file"
|
|
17
|
+
DNOMSGFILE = 9, "Could not open message storage file"
|
|
18
|
+
DDIRSEMERR = 10, "Error on directory lock semaphore"
|
|
19
|
+
DMSGTIMEOUT = 11, "Timeout waiting for new messages"
|
|
20
|
+
DNONETLIST = 12, "Could not open network list file"
|
|
21
|
+
DNOSRCHCRIT = 13, "Could not open search criteria file"
|
|
22
|
+
DBADSINCE = 14, "Bad since time in search criteria file"
|
|
23
|
+
DBADUNTIL = 15, "Bad until time in search criteria file"
|
|
24
|
+
DBADNLIST = 16, "Bad network list in search criteria file"
|
|
25
|
+
DBADADDR = 17, "Bad DCP address in search criteria file"
|
|
26
|
+
DBADEMAIL = 18, "Bad electronic mail value in search criteria file"
|
|
27
|
+
DBADRTRAN = 19, "Bad retransmitted value in search criteria file"
|
|
28
|
+
DNLISTXCD = 20, "Number of network lists exceeded"
|
|
29
|
+
DADDRXCD = 21, "Number of DCP addresses exceeded"
|
|
30
|
+
DNOLRGSLAST = 22, "Could not open last read access file"
|
|
31
|
+
DWRONGMSG = 23, "Message doesn't correspond with directory entry"
|
|
32
|
+
DNOMOREPROC = 24, "Can't attach: No more processes allowed"
|
|
33
|
+
DBADDAPSSTAT = 25, "Bad DAPS status specified in search criteria."
|
|
34
|
+
DBADTIMEOUT = 26, "Bad TIMEOUT value in search crit file."
|
|
35
|
+
DCANTIOCTL = 27, "Cannot ioctl() the open serial port."
|
|
36
|
+
DUNTILDRS = 28, "Specified 'until' time reached"
|
|
37
|
+
DBADCHANNEL = 29, "Bad GOES channel number specified in search crit"
|
|
38
|
+
DCANTOPENSER = 30, "Can't open specified serial port."
|
|
39
|
+
DBADDCPNAME = 31, "Unrecognized DCP name in search criteria"
|
|
40
|
+
DNONAMELIST = 32, "Cannot attach to name list shared memory."
|
|
41
|
+
DIDXFILEIO = 33, "Index file I/O error"
|
|
42
|
+
# DNOSRCHSEM = 34, "Bad search-criteria data"
|
|
43
|
+
DBADSEARCHCRIT = 34, "Bad search-criteria data"
|
|
44
|
+
DUNTIL = 35, "Specified 'until' time reached"
|
|
45
|
+
DJAVAIF = 36, "Error in Java - Native Interface"
|
|
46
|
+
DNOTATTACHED = 37, "Not attached to LRGS native interface"
|
|
47
|
+
DBADKEYWORD = 38, "Bad keyword"
|
|
48
|
+
DPARSEERROR = 39, "Error parsing input file"
|
|
49
|
+
DNONAMELISTSEM = 40, "Cannot attach to name list semaphore."
|
|
50
|
+
DBADINPUTFILE = 41, "Cannot open or read specified input file"
|
|
51
|
+
DARCFILEIO = 42, "Archive file I/O error"
|
|
52
|
+
DNOARCFILE = 43, "Archive file not opened"
|
|
53
|
+
DICPIOCTL = 44, "Error on ICP188 ioctl call"
|
|
54
|
+
DICPIOERR = 45, "Error on ICP188 I/O call"
|
|
55
|
+
DINVALIDUSER = 46, "Invalid DDS User"
|
|
56
|
+
DDDSAUTHFAILED = 47, "DDS Authentication failed"
|
|
57
|
+
DDDSINTERNAL = 48, "DDS Internal Error (connection will close)"
|
|
58
|
+
DDDSFATAL = 49, "DDS Fatal Server Error (retry later)"
|
|
59
|
+
DNOSUCHSOURCE = 50, "No such data source"
|
|
60
|
+
DALREADYATTACHED = 51, "User already attached (mult disallowed)"
|
|
61
|
+
DNOSUCHFILE = 52, "No such file"
|
|
62
|
+
DTOOMANYDCPS = 53, "Too many DCPs for real-time stream"
|
|
63
|
+
DBADPASSWORD = 54, "Password does not meet local requirements."
|
|
64
|
+
DSTRONGREQUIRED = 55, "Server requires strong encryption algorithm"
|
|
65
|
+
|
|
66
|
+
def __new__(cls, *args, **kwargs):
|
|
67
|
+
obj = object.__new__(cls)
|
|
68
|
+
obj._value_ = args[0]
|
|
69
|
+
return obj
|
|
70
|
+
|
|
71
|
+
# ignore the first param since it's already set by __new__
|
|
72
|
+
def __init__(self, _: str, description: str = None):
|
|
73
|
+
self.__description = description
|
|
74
|
+
|
|
75
|
+
def __str__(self):
|
|
76
|
+
return self.value
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def description(self):
|
|
80
|
+
return self.__description
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ServerError:
|
|
84
|
+
def __init__(self, message: str, server_code_no: int = 0, system_code_no: int = 0):
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
:param message:
|
|
88
|
+
:param server_code_no:
|
|
89
|
+
:param system_code_no:
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
self.message = message
|
|
93
|
+
self.server_code_no = server_code_no
|
|
94
|
+
self.system_code_no = system_code_no
|
|
95
|
+
self.is_end_of_message = self.is_end_of_message()
|
|
96
|
+
|
|
97
|
+
def is_end_of_message(self):
|
|
98
|
+
if self.server_code_no in (
|
|
99
|
+
ServerErrorCode.DUNTIL.value,
|
|
100
|
+
ServerErrorCode.DUNTILDRS.value,
|
|
101
|
+
):
|
|
102
|
+
return True
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def description(self):
|
|
107
|
+
return ServerErrorCode(self.server_code_no).description
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def parse(message: bytes):
|
|
111
|
+
"""
|
|
112
|
+
Parse ServerError from error_string
|
|
113
|
+
|
|
114
|
+
:param message: bytes response returned from server
|
|
115
|
+
:return: object of ServerError class
|
|
116
|
+
"""
|
|
117
|
+
if not message.startswith(b"?"):
|
|
118
|
+
# Not a server error
|
|
119
|
+
return ServerError("")
|
|
120
|
+
error_string = ByteUtil.extract_string(message)
|
|
121
|
+
split_error_string = error_string[1:].split(",", maxsplit=2)
|
|
122
|
+
sever_code_no, system_code_no, error_string = [
|
|
123
|
+
x.strip() for x in split_error_string
|
|
124
|
+
]
|
|
125
|
+
return ServerError(error_string, int(sever_code_no), int(system_code_no))
|
|
126
|
+
|
|
127
|
+
def raise_exception(self):
|
|
128
|
+
raise ProtocolError(self.__str__())
|
|
129
|
+
|
|
130
|
+
def __str__(self):
|
|
131
|
+
if self.system_code_no == 0 and self.server_code_no == 0:
|
|
132
|
+
return "No Server Error"
|
|
133
|
+
|
|
134
|
+
server_error_code = ServerErrorCode(self.server_code_no)
|
|
135
|
+
r = f"System Code #{self.system_code_no}; "
|
|
136
|
+
r += f"Server Code #{server_error_code.value} - {self.message} ({server_error_code.description})"
|
|
137
|
+
return r
|
|
138
|
+
|
|
139
|
+
def __eq__(self, other):
|
|
140
|
+
return (
|
|
141
|
+
self.server_code_no == other.server_code_no
|
|
142
|
+
and self.system_code_no == other.system_code_no
|
|
143
|
+
and self.message == other.message
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ProtocolError(Exception):
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class LddsMessageError(Exception):
|
|
152
|
+
pass
|