aprsd 4.0.1__py3-none-any.whl → 4.1.0__py3-none-any.whl
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.
- aprsd/client/drivers/aprsis.py +87 -35
- aprsd/client/drivers/fake.py +5 -0
- aprsd/client/drivers/kiss.py +45 -20
- aprsd/client/kiss.py +29 -25
- aprsd/cmds/listen.py +84 -91
- aprsd/cmds/server.py +1 -1
- aprsd/conf/common.py +100 -101
- aprsd/conf/log.py +27 -22
- aprsd/log/log.py +24 -14
- aprsd/packets/__init__.py +6 -0
- aprsd/packets/core.py +5 -2
- aprsd/packets/filter.py +58 -0
- aprsd/packets/filters/__init__.py +0 -0
- aprsd/packets/filters/dupe_filter.py +68 -0
- aprsd/packets/filters/packet_type.py +53 -0
- aprsd/packets/packet_list.py +33 -27
- aprsd/plugins/fortune.py +21 -15
- aprsd/threads/__init__.py +1 -2
- aprsd/threads/rx.py +83 -75
- aprsd/threads/stats.py +4 -9
- aprsd/utils/objectstore.py +12 -13
- {aprsd-4.0.1.dist-info → aprsd-4.1.0.dist-info}/METADATA +1 -1
- {aprsd-4.0.1.dist-info → aprsd-4.1.0.dist-info}/RECORD +28 -24
- {aprsd-4.0.1.dist-info → aprsd-4.1.0.dist-info}/AUTHORS +0 -0
- {aprsd-4.0.1.dist-info → aprsd-4.1.0.dist-info}/LICENSE +0 -0
- {aprsd-4.0.1.dist-info → aprsd-4.1.0.dist-info}/WHEEL +0 -0
- {aprsd-4.0.1.dist-info → aprsd-4.1.0.dist-info}/entry_points.txt +0 -0
- {aprsd-4.0.1.dist-info → aprsd-4.1.0.dist-info}/top_level.txt +0 -0
aprsd/conf/common.py
CHANGED
@@ -3,220 +3,219 @@ from pathlib import Path
|
|
3
3
|
from oslo_config import cfg
|
4
4
|
|
5
5
|
home = str(Path.home())
|
6
|
-
DEFAULT_CONFIG_DIR = f
|
7
|
-
APRSD_DEFAULT_MAGIC_WORD =
|
6
|
+
DEFAULT_CONFIG_DIR = f'{home}/.config/aprsd/'
|
7
|
+
APRSD_DEFAULT_MAGIC_WORD = 'CHANGEME!!!'
|
8
8
|
|
9
9
|
watch_list_group = cfg.OptGroup(
|
10
|
-
name=
|
11
|
-
title=
|
10
|
+
name='watch_list',
|
11
|
+
title='Watch List settings',
|
12
12
|
)
|
13
13
|
|
14
14
|
registry_group = cfg.OptGroup(
|
15
|
-
name=
|
16
|
-
title=
|
15
|
+
name='aprs_registry',
|
16
|
+
title='APRS Registry settings',
|
17
17
|
)
|
18
18
|
|
19
19
|
aprsd_opts = [
|
20
20
|
cfg.StrOpt(
|
21
|
-
|
21
|
+
'callsign',
|
22
22
|
required=True,
|
23
|
-
help=
|
23
|
+
help='Callsign to use for messages sent by APRSD',
|
24
24
|
),
|
25
25
|
cfg.BoolOpt(
|
26
|
-
|
26
|
+
'enable_save',
|
27
27
|
default=True,
|
28
|
-
help=
|
28
|
+
help='Enable saving of watch list, packet tracker between restarts.',
|
29
29
|
),
|
30
30
|
cfg.StrOpt(
|
31
|
-
|
31
|
+
'save_location',
|
32
32
|
default=DEFAULT_CONFIG_DIR,
|
33
|
-
help=
|
33
|
+
help='Save location for packet tracking files.',
|
34
34
|
),
|
35
35
|
cfg.BoolOpt(
|
36
|
-
|
36
|
+
'trace_enabled',
|
37
37
|
default=False,
|
38
|
-
help=
|
38
|
+
help='Enable code tracing',
|
39
39
|
),
|
40
40
|
cfg.StrOpt(
|
41
|
-
|
42
|
-
default=
|
43
|
-
help=
|
41
|
+
'units',
|
42
|
+
default='imperial',
|
43
|
+
help='Units for display, imperial or metric',
|
44
44
|
),
|
45
45
|
cfg.IntOpt(
|
46
|
-
|
46
|
+
'ack_rate_limit_period',
|
47
47
|
default=1,
|
48
|
-
help=
|
49
|
-
|
50
|
-
|
48
|
+
help='The wait period in seconds per Ack packet being sent.'
|
49
|
+
'1 means 1 ack packet per second allowed.'
|
50
|
+
'2 means 1 pack packet every 2 seconds allowed',
|
51
51
|
),
|
52
52
|
cfg.IntOpt(
|
53
|
-
|
53
|
+
'msg_rate_limit_period',
|
54
54
|
default=2,
|
55
|
-
help=
|
56
|
-
|
57
|
-
|
55
|
+
help='Wait period in seconds per non AckPacket being sent.'
|
56
|
+
'2 means 1 packet every 2 seconds allowed.'
|
57
|
+
'5 means 1 pack packet every 5 seconds allowed',
|
58
58
|
),
|
59
59
|
cfg.IntOpt(
|
60
|
-
|
60
|
+
'packet_dupe_timeout',
|
61
61
|
default=300,
|
62
|
-
help=
|
62
|
+
help='The number of seconds before a packet is not considered a duplicate.',
|
63
63
|
),
|
64
64
|
cfg.BoolOpt(
|
65
|
-
|
65
|
+
'enable_beacon',
|
66
66
|
default=False,
|
67
|
-
help=
|
68
|
-
|
67
|
+
help='Enable sending of a GPS Beacon packet to locate this service. '
|
68
|
+
'Requires latitude and longitude to be set.',
|
69
69
|
),
|
70
70
|
cfg.IntOpt(
|
71
|
-
|
71
|
+
'beacon_interval',
|
72
72
|
default=1800,
|
73
|
-
help=
|
73
|
+
help='The number of seconds between beacon packets.',
|
74
74
|
),
|
75
75
|
cfg.StrOpt(
|
76
|
-
|
77
|
-
default=
|
78
|
-
help=
|
76
|
+
'beacon_symbol',
|
77
|
+
default='/',
|
78
|
+
help='The symbol to use for the GPS Beacon packet. See: http://www.aprs.net/vm/DOS/SYMBOLS.HTM',
|
79
79
|
),
|
80
80
|
cfg.StrOpt(
|
81
|
-
|
81
|
+
'latitude',
|
82
82
|
default=None,
|
83
|
-
help=
|
83
|
+
help='Latitude for the GPS Beacon button. If not set, the button will not be enabled.',
|
84
84
|
),
|
85
85
|
cfg.StrOpt(
|
86
|
-
|
86
|
+
'longitude',
|
87
87
|
default=None,
|
88
|
-
help=
|
88
|
+
help='Longitude for the GPS Beacon button. If not set, the button will not be enabled.',
|
89
89
|
),
|
90
90
|
cfg.StrOpt(
|
91
|
-
|
92
|
-
choices=[
|
93
|
-
default=
|
91
|
+
'log_packet_format',
|
92
|
+
choices=['compact', 'multiline', 'both'],
|
93
|
+
default='compact',
|
94
94
|
help="When logging packets 'compact' will use a single line formatted for each packet."
|
95
95
|
"'multiline' will use multiple lines for each packet and is the traditional format."
|
96
|
-
|
96
|
+
'both will log both compact and multiline.',
|
97
97
|
),
|
98
98
|
cfg.IntOpt(
|
99
|
-
|
99
|
+
'default_packet_send_count',
|
100
100
|
default=3,
|
101
|
-
help=
|
101
|
+
help='The number of times to send a non ack packet before giving up.',
|
102
102
|
),
|
103
103
|
cfg.IntOpt(
|
104
|
-
|
104
|
+
'default_ack_send_count',
|
105
105
|
default=3,
|
106
|
-
help=
|
106
|
+
help='The number of times to send an ack packet in response to recieving a packet.',
|
107
107
|
),
|
108
108
|
cfg.IntOpt(
|
109
|
-
|
109
|
+
'packet_list_maxlen',
|
110
110
|
default=100,
|
111
|
-
help=
|
111
|
+
help='The maximum number of packets to store in the packet list.',
|
112
112
|
),
|
113
113
|
cfg.IntOpt(
|
114
|
-
|
114
|
+
'packet_list_stats_maxlen',
|
115
115
|
default=20,
|
116
|
-
help=
|
116
|
+
help='The maximum number of packets to send in the stats dict for admin ui. -1 means no max.',
|
117
117
|
),
|
118
118
|
cfg.BoolOpt(
|
119
|
-
|
119
|
+
'enable_seen_list',
|
120
120
|
default=True,
|
121
|
-
help=
|
122
|
-
|
121
|
+
help='Enable the Callsign seen list tracking feature. This allows aprsd to keep track of '
|
122
|
+
'callsigns that have been seen and when they were last seen.',
|
123
123
|
),
|
124
124
|
cfg.BoolOpt(
|
125
|
-
|
125
|
+
'enable_packet_logging',
|
126
126
|
default=True,
|
127
|
-
help=
|
127
|
+
help='Set this to False, to disable logging of packets to the log file.',
|
128
128
|
),
|
129
129
|
cfg.BoolOpt(
|
130
|
-
|
130
|
+
'load_help_plugin',
|
131
131
|
default=True,
|
132
|
-
help=
|
132
|
+
help='Set this to False to disable the help plugin.',
|
133
133
|
),
|
134
134
|
cfg.BoolOpt(
|
135
|
-
|
135
|
+
'enable_sending_ack_packets',
|
136
136
|
default=True,
|
137
|
-
help=
|
138
|
-
|
137
|
+
help='Set this to False, to disable sending of ack packets. This will entirely stop'
|
138
|
+
'APRSD from sending ack packets.',
|
139
139
|
),
|
140
140
|
]
|
141
141
|
|
142
142
|
watch_list_opts = [
|
143
143
|
cfg.BoolOpt(
|
144
|
-
|
144
|
+
'enabled',
|
145
145
|
default=False,
|
146
|
-
help=
|
147
|
-
|
148
|
-
|
146
|
+
help='Enable the watch list feature. Still have to enable '
|
147
|
+
'the correct plugin. Built-in plugin to use is '
|
148
|
+
'aprsd.plugins.notify.NotifyPlugin',
|
149
149
|
),
|
150
150
|
cfg.ListOpt(
|
151
|
-
|
152
|
-
help=
|
151
|
+
'callsigns',
|
152
|
+
help='Callsigns to watch for messsages',
|
153
153
|
),
|
154
154
|
cfg.StrOpt(
|
155
|
-
|
156
|
-
help=
|
155
|
+
'alert_callsign',
|
156
|
+
help='The Ham Callsign to send messages to for watch list alerts.',
|
157
157
|
),
|
158
158
|
cfg.IntOpt(
|
159
|
-
|
159
|
+
'packet_keep_count',
|
160
160
|
default=10,
|
161
|
-
help=
|
161
|
+
help='The number of packets to store.',
|
162
162
|
),
|
163
163
|
cfg.IntOpt(
|
164
|
-
|
164
|
+
'alert_time_seconds',
|
165
165
|
default=3600,
|
166
|
-
help=
|
167
|
-
"users in callsigns.",
|
166
|
+
help='Time to wait before alert is sent on new message for users in callsigns.',
|
168
167
|
),
|
169
168
|
]
|
170
169
|
|
171
170
|
|
172
171
|
enabled_plugins_opts = [
|
173
172
|
cfg.ListOpt(
|
174
|
-
|
173
|
+
'enabled_plugins',
|
175
174
|
default=[
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
175
|
+
'aprsd.plugins.fortune.FortunePlugin',
|
176
|
+
'aprsd.plugins.location.LocationPlugin',
|
177
|
+
'aprsd.plugins.ping.PingPlugin',
|
178
|
+
'aprsd.plugins.time.TimePlugin',
|
179
|
+
'aprsd.plugins.weather.OWMWeatherPlugin',
|
180
|
+
'aprsd.plugins.version.VersionPlugin',
|
181
|
+
'aprsd.plugins.notify.NotifySeenPlugin',
|
183
182
|
],
|
184
|
-
help=
|
185
|
-
|
186
|
-
|
183
|
+
help='Comma separated list of enabled plugins for APRSD.'
|
184
|
+
'To enable installed external plugins add them here.'
|
185
|
+
'The full python path to the class name must be used',
|
187
186
|
),
|
188
187
|
]
|
189
188
|
|
190
189
|
registry_opts = [
|
191
190
|
cfg.BoolOpt(
|
192
|
-
|
191
|
+
'enabled',
|
193
192
|
default=False,
|
194
|
-
help=
|
193
|
+
help='Enable sending aprs registry information. This will let the '
|
195
194
|
"APRS registry know about your service and it's uptime. "
|
196
|
-
|
197
|
-
|
195
|
+
'No personal information is sent, just the callsign, uptime and description. '
|
196
|
+
'The service callsign is the callsign set in [DEFAULT] section.',
|
198
197
|
),
|
199
198
|
cfg.StrOpt(
|
200
|
-
|
199
|
+
'description',
|
201
200
|
default=None,
|
202
|
-
help=
|
203
|
-
|
204
|
-
|
201
|
+
help='Description of the service to send to the APRS registry. '
|
202
|
+
'This is what will show up in the APRS registry.'
|
203
|
+
'If not set, the description will be the same as the callsign.',
|
205
204
|
),
|
206
205
|
cfg.StrOpt(
|
207
|
-
|
208
|
-
default=
|
209
|
-
help=
|
206
|
+
'registry_url',
|
207
|
+
default='https://aprs.hemna.com/api/v1/registry',
|
208
|
+
help='The APRS registry domain name to send the information to.',
|
210
209
|
),
|
211
210
|
cfg.StrOpt(
|
212
|
-
|
211
|
+
'service_website',
|
213
212
|
default=None,
|
214
|
-
help=
|
213
|
+
help='The website for your APRS service to send to the APRS registry.',
|
215
214
|
),
|
216
215
|
cfg.IntOpt(
|
217
|
-
|
216
|
+
'frequency_seconds',
|
218
217
|
default=3600,
|
219
|
-
help=
|
218
|
+
help='The frequency in seconds to send the APRS registry information.',
|
220
219
|
),
|
221
220
|
]
|
222
221
|
|
@@ -232,7 +231,7 @@ def register_opts(config):
|
|
232
231
|
|
233
232
|
def list_opts():
|
234
233
|
return {
|
235
|
-
|
234
|
+
'DEFAULT': (aprsd_opts + enabled_plugins_opts),
|
236
235
|
watch_list_group.name: watch_list_opts,
|
237
236
|
registry_group.name: registry_opts,
|
238
237
|
}
|
aprsd/conf/log.py
CHANGED
@@ -7,47 +7,52 @@ import logging
|
|
7
7
|
from oslo_config import cfg
|
8
8
|
|
9
9
|
LOG_LEVELS = {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
'CRITICAL': logging.CRITICAL,
|
11
|
+
'ERROR': logging.ERROR,
|
12
|
+
'WARNING': logging.WARNING,
|
13
|
+
'INFO': logging.INFO,
|
14
|
+
'DEBUG': logging.DEBUG,
|
15
15
|
}
|
16
16
|
|
17
|
-
DEFAULT_DATE_FORMAT =
|
17
|
+
DEFAULT_DATE_FORMAT = '%m/%d/%Y %I:%M:%S %p'
|
18
18
|
DEFAULT_LOG_FORMAT = (
|
19
|
-
|
20
|
-
|
19
|
+
'[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s]'
|
20
|
+
' %(message)s - [%(pathname)s:%(lineno)d]'
|
21
21
|
)
|
22
22
|
|
23
23
|
DEFAULT_LOG_FORMAT = (
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
'<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | '
|
25
|
+
'<yellow>{thread.name: <18}</yellow> | '
|
26
|
+
'<level>{level: <8}</level> | '
|
27
|
+
'<level>{message}</level> | '
|
28
|
+
'<cyan>{name}</cyan>:<cyan>{function:}</cyan>:<magenta>{line:}</magenta>'
|
29
29
|
)
|
30
30
|
|
31
31
|
logging_group = cfg.OptGroup(
|
32
|
-
name=
|
33
|
-
title=
|
32
|
+
name='logging',
|
33
|
+
title='Logging options',
|
34
34
|
)
|
35
35
|
logging_opts = [
|
36
36
|
cfg.StrOpt(
|
37
|
-
|
37
|
+
'logfile',
|
38
38
|
default=None,
|
39
|
-
help=
|
39
|
+
help='File to log to',
|
40
40
|
),
|
41
41
|
cfg.StrOpt(
|
42
|
-
|
42
|
+
'logformat',
|
43
43
|
default=DEFAULT_LOG_FORMAT,
|
44
|
-
help=
|
44
|
+
help='Log file format, unless rich_logging enabled.',
|
45
45
|
),
|
46
46
|
cfg.StrOpt(
|
47
|
-
|
48
|
-
default=
|
47
|
+
'log_level',
|
48
|
+
default='INFO',
|
49
49
|
choices=LOG_LEVELS.keys(),
|
50
|
-
help=
|
50
|
+
help='Log level for logging of events.',
|
51
|
+
),
|
52
|
+
cfg.BoolOpt(
|
53
|
+
'enable_color',
|
54
|
+
default=True,
|
55
|
+
help='Enable ANSI color codes in logging',
|
51
56
|
),
|
52
57
|
]
|
53
58
|
|
aprsd/log/log.py
CHANGED
@@ -63,11 +63,21 @@ def setup_logging(loglevel=None, quiet=False):
|
|
63
63
|
|
64
64
|
# We don't really want to see the aprslib parsing debug output.
|
65
65
|
disable_list = [
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
'aprslib',
|
67
|
+
'aprslib.parsing',
|
68
|
+
'aprslib.exceptions',
|
69
69
|
]
|
70
70
|
|
71
|
+
chardet_list = [
|
72
|
+
'chardet',
|
73
|
+
'chardet.charsetprober',
|
74
|
+
'chardet.eucjpprober',
|
75
|
+
]
|
76
|
+
|
77
|
+
for name in chardet_list:
|
78
|
+
disable = logging.getLogger(name)
|
79
|
+
disable.setLevel(logging.ERROR)
|
80
|
+
|
71
81
|
# remove every other logger's handlers
|
72
82
|
# and propagate to root logger
|
73
83
|
for name in logging.root.manager.loggerDict.keys():
|
@@ -76,24 +86,24 @@ def setup_logging(loglevel=None, quiet=False):
|
|
76
86
|
|
77
87
|
handlers = [
|
78
88
|
{
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
89
|
+
'sink': sys.stdout,
|
90
|
+
'serialize': False,
|
91
|
+
'format': CONF.logging.logformat,
|
92
|
+
'colorize': CONF.logging.enable_color,
|
93
|
+
'level': log_level,
|
84
94
|
},
|
85
95
|
]
|
86
96
|
if CONF.logging.logfile:
|
87
97
|
handlers.append(
|
88
98
|
{
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
99
|
+
'sink': CONF.logging.logfile,
|
100
|
+
'serialize': False,
|
101
|
+
'format': CONF.logging.logformat,
|
102
|
+
'colorize': False,
|
103
|
+
'level': log_level,
|
94
104
|
},
|
95
105
|
)
|
96
106
|
|
97
107
|
# configure loguru
|
98
108
|
logger.configure(handlers=handlers)
|
99
|
-
logger.level(
|
109
|
+
logger.level('DEBUG', color='<fg #BABABA>')
|
aprsd/packets/__init__.py
CHANGED
@@ -15,6 +15,8 @@ from aprsd.packets.core import ( # noqa: F401
|
|
15
15
|
WeatherPacket,
|
16
16
|
factory,
|
17
17
|
)
|
18
|
+
from aprsd.packets.filter import PacketFilter
|
19
|
+
from aprsd.packets.filters.dupe_filter import DupePacketFilter
|
18
20
|
from aprsd.packets.packet_list import PacketList # noqa: F401
|
19
21
|
from aprsd.packets.seen_list import SeenList # noqa: F401
|
20
22
|
from aprsd.packets.tracker import PacketTrack # noqa: F401
|
@@ -26,5 +28,9 @@ collector.PacketCollector().register(SeenList)
|
|
26
28
|
collector.PacketCollector().register(PacketTrack)
|
27
29
|
collector.PacketCollector().register(WatchList)
|
28
30
|
|
31
|
+
# Register all the packet filters for normal processing
|
32
|
+
# For specific commands you can deregister these if you don't want them.
|
33
|
+
PacketFilter().register(DupePacketFilter)
|
34
|
+
|
29
35
|
|
30
36
|
NULL_MESSAGE = -1
|
aprsd/packets/core.py
CHANGED
@@ -106,6 +106,8 @@ class Packet:
|
|
106
106
|
last_send_time: float = field(repr=False, default=0, compare=False, hash=False)
|
107
107
|
# Was the packet acked?
|
108
108
|
acked: bool = field(repr=False, default=False, compare=False, hash=False)
|
109
|
+
# Was the packet previously processed (for dupe checking)
|
110
|
+
processed: bool = field(repr=False, default=False, compare=False, hash=False)
|
109
111
|
|
110
112
|
# Do we allow this packet to be saved to send later?
|
111
113
|
allow_delay: bool = field(repr=False, default=True, compare=False, hash=False)
|
@@ -186,12 +188,11 @@ class Packet:
|
|
186
188
|
|
187
189
|
def __repr__(self) -> str:
|
188
190
|
"""Build the repr version of the packet."""
|
189
|
-
|
191
|
+
return (
|
190
192
|
f"{self.__class__.__name__}:"
|
191
193
|
f" From: {self.from_call} "
|
192
194
|
f" To: {self.to_call}"
|
193
195
|
)
|
194
|
-
return repr
|
195
196
|
|
196
197
|
|
197
198
|
@dataclass_json
|
@@ -694,6 +695,8 @@ class UnknownPacket:
|
|
694
695
|
path: List[str] = field(default_factory=list, compare=False, hash=False)
|
695
696
|
packet_type: Optional[str] = field(default=None)
|
696
697
|
via: Optional[str] = field(default=None, compare=False, hash=False)
|
698
|
+
# Was the packet previously processed (for dupe checking)
|
699
|
+
processed: bool = field(repr=False, default=False, compare=False, hash=False)
|
697
700
|
|
698
701
|
@property
|
699
702
|
def key(self) -> str:
|
aprsd/packets/filter.py
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Callable, Protocol, runtime_checkable, Union, Dict
|
3
|
+
|
4
|
+
from aprsd.packets import core
|
5
|
+
from aprsd.utils import singleton
|
6
|
+
|
7
|
+
LOG = logging.getLogger("APRSD")
|
8
|
+
|
9
|
+
|
10
|
+
@runtime_checkable
|
11
|
+
class PacketFilterProtocol(Protocol):
|
12
|
+
"""Protocol API for a packet filter class.
|
13
|
+
"""
|
14
|
+
def filter(self, packet: type[core.Packet]) -> Union[type[core.Packet], None]:
|
15
|
+
"""When we get a packet from the network.
|
16
|
+
|
17
|
+
Return a Packet object if the filter passes. Return None if the
|
18
|
+
Packet is filtered out.
|
19
|
+
"""
|
20
|
+
...
|
21
|
+
|
22
|
+
|
23
|
+
@singleton
|
24
|
+
class PacketFilter:
|
25
|
+
|
26
|
+
def __init__(self):
|
27
|
+
self.filters: Dict[str, Callable] = {}
|
28
|
+
|
29
|
+
def register(self, packet_filter: Callable) -> None:
|
30
|
+
if not isinstance(packet_filter, PacketFilterProtocol):
|
31
|
+
raise TypeError(f"class {packet_filter} is not a PacketFilterProtocol object")
|
32
|
+
|
33
|
+
if packet_filter not in self.filters:
|
34
|
+
self.filters[packet_filter] = packet_filter()
|
35
|
+
|
36
|
+
def unregister(self, packet_filter: Callable) -> None:
|
37
|
+
if not isinstance(packet_filter, PacketFilterProtocol):
|
38
|
+
raise TypeError(f"class {packet_filter} is not a PacketFilterProtocol object")
|
39
|
+
if packet_filter in self.filters:
|
40
|
+
del self.filters[packet_filter]
|
41
|
+
|
42
|
+
def filter(self, packet: type[core.Packet]) -> Union[type[core.Packet], None]:
|
43
|
+
"""Run through each of the filters.
|
44
|
+
|
45
|
+
This will step through each registered filter class
|
46
|
+
and call filter on it.
|
47
|
+
|
48
|
+
If the filter object returns None, we are done filtering.
|
49
|
+
If the filter object returns the packet, we continue filtering.
|
50
|
+
"""
|
51
|
+
for packet_filter in self.filters:
|
52
|
+
try:
|
53
|
+
if not self.filters[packet_filter].filter(packet):
|
54
|
+
LOG.debug(f"{self.filters[packet_filter].__class__.__name__} dropped {packet.__class__.__name__}:{packet.human_info}")
|
55
|
+
return None
|
56
|
+
except Exception as ex:
|
57
|
+
LOG.error(f"{packet_filter.__clas__.__name__} failed filtering packet {packet.__class__.__name__} : {ex}")
|
58
|
+
return packet
|
File without changes
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Union
|
3
|
+
|
4
|
+
from oslo_config import cfg
|
5
|
+
|
6
|
+
from aprsd import packets
|
7
|
+
from aprsd.packets import core
|
8
|
+
|
9
|
+
CONF = cfg.CONF
|
10
|
+
LOG = logging.getLogger('APRSD')
|
11
|
+
|
12
|
+
|
13
|
+
class DupePacketFilter:
|
14
|
+
"""This is a packet filter to detect duplicate packets.
|
15
|
+
|
16
|
+
This Uses the PacketList object to see if a packet exists
|
17
|
+
already. If it does exist in the PacketList, then we need to
|
18
|
+
check the flag on the packet to see if it's been processed before.
|
19
|
+
If the packet has been processed already within the allowed
|
20
|
+
timeframe, then it's a dupe.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def filter(self, packet: type[core.Packet]) -> Union[type[core.Packet], None]:
|
24
|
+
# LOG.debug(f"{self.__class__.__name__}.filter called for packet {packet}")
|
25
|
+
"""Filter a packet out if it's already been seen and processed."""
|
26
|
+
if isinstance(packet, core.AckPacket):
|
27
|
+
# We don't need to drop AckPackets, those should be
|
28
|
+
# processed.
|
29
|
+
# Send the AckPacket to the queue for processing elsewhere.
|
30
|
+
return packet
|
31
|
+
else:
|
32
|
+
# Make sure we aren't re-processing the same packet
|
33
|
+
# For RF based APRS Clients we can get duplicate packets
|
34
|
+
# So we need to track them and not process the dupes.
|
35
|
+
pkt_list = packets.PacketList()
|
36
|
+
found = False
|
37
|
+
try:
|
38
|
+
# Find the packet in the list of already seen packets
|
39
|
+
# Based on the packet.key
|
40
|
+
found = pkt_list.find(packet)
|
41
|
+
if not packet.msgNo:
|
42
|
+
# If the packet doesn't have a message id
|
43
|
+
# then there is no reliable way to detect
|
44
|
+
# if it's a dupe, so we just pass it on.
|
45
|
+
# it shouldn't get acked either.
|
46
|
+
found = False
|
47
|
+
except KeyError:
|
48
|
+
found = False
|
49
|
+
|
50
|
+
if not found:
|
51
|
+
# We haven't seen this packet before, so we process it.
|
52
|
+
return packet
|
53
|
+
|
54
|
+
if not packet.processed:
|
55
|
+
# We haven't processed this packet through the plugins.
|
56
|
+
return packet
|
57
|
+
elif packet.timestamp - found.timestamp < CONF.packet_dupe_timeout:
|
58
|
+
# If the packet came in within N seconds of the
|
59
|
+
# Last time seeing the packet, then we drop it as a dupe.
|
60
|
+
LOG.warning(
|
61
|
+
f'Packet {packet.from_call}:{packet.msgNo} already tracked, dropping.'
|
62
|
+
)
|
63
|
+
else:
|
64
|
+
LOG.warning(
|
65
|
+
f'Packet {packet.from_call}:{packet.msgNo} already tracked '
|
66
|
+
f'but older than {CONF.packet_dupe_timeout} seconds. processing.',
|
67
|
+
)
|
68
|
+
return packet
|