aprsd 4.0.2__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/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"{home}/.config/aprsd/"
7
- APRSD_DEFAULT_MAGIC_WORD = "CHANGEME!!!"
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="watch_list",
11
- title="Watch List settings",
10
+ name='watch_list',
11
+ title='Watch List settings',
12
12
  )
13
13
 
14
14
  registry_group = cfg.OptGroup(
15
- name="aprs_registry",
16
- title="APRS Registry settings",
15
+ name='aprs_registry',
16
+ title='APRS Registry settings',
17
17
  )
18
18
 
19
19
  aprsd_opts = [
20
20
  cfg.StrOpt(
21
- "callsign",
21
+ 'callsign',
22
22
  required=True,
23
- help="Callsign to use for messages sent by APRSD",
23
+ help='Callsign to use for messages sent by APRSD',
24
24
  ),
25
25
  cfg.BoolOpt(
26
- "enable_save",
26
+ 'enable_save',
27
27
  default=True,
28
- help="Enable saving of watch list, packet tracker between restarts.",
28
+ help='Enable saving of watch list, packet tracker between restarts.',
29
29
  ),
30
30
  cfg.StrOpt(
31
- "save_location",
31
+ 'save_location',
32
32
  default=DEFAULT_CONFIG_DIR,
33
- help="Save location for packet tracking files.",
33
+ help='Save location for packet tracking files.',
34
34
  ),
35
35
  cfg.BoolOpt(
36
- "trace_enabled",
36
+ 'trace_enabled',
37
37
  default=False,
38
- help="Enable code tracing",
38
+ help='Enable code tracing',
39
39
  ),
40
40
  cfg.StrOpt(
41
- "units",
42
- default="imperial",
43
- help="Units for display, imperial or metric",
41
+ 'units',
42
+ default='imperial',
43
+ help='Units for display, imperial or metric',
44
44
  ),
45
45
  cfg.IntOpt(
46
- "ack_rate_limit_period",
46
+ 'ack_rate_limit_period',
47
47
  default=1,
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",
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
- "msg_rate_limit_period",
53
+ 'msg_rate_limit_period',
54
54
  default=2,
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",
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
- "packet_dupe_timeout",
60
+ 'packet_dupe_timeout',
61
61
  default=300,
62
- help="The number of seconds before a packet is not considered a duplicate.",
62
+ help='The number of seconds before a packet is not considered a duplicate.',
63
63
  ),
64
64
  cfg.BoolOpt(
65
- "enable_beacon",
65
+ 'enable_beacon',
66
66
  default=False,
67
- help="Enable sending of a GPS Beacon packet to locate this service. "
68
- "Requires latitude and longitude to be set.",
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
- "beacon_interval",
71
+ 'beacon_interval',
72
72
  default=1800,
73
- help="The number of seconds between beacon packets.",
73
+ help='The number of seconds between beacon packets.',
74
74
  ),
75
75
  cfg.StrOpt(
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",
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
- "latitude",
81
+ 'latitude',
82
82
  default=None,
83
- help="Latitude for the GPS Beacon button. If not set, the button will not be enabled.",
83
+ help='Latitude for the GPS Beacon button. If not set, the button will not be enabled.',
84
84
  ),
85
85
  cfg.StrOpt(
86
- "longitude",
86
+ 'longitude',
87
87
  default=None,
88
- help="Longitude for the GPS Beacon button. If not set, the button will not be enabled.",
88
+ help='Longitude for the GPS Beacon button. If not set, the button will not be enabled.',
89
89
  ),
90
90
  cfg.StrOpt(
91
- "log_packet_format",
92
- choices=["compact", "multiline", "both"],
93
- default="compact",
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
- "both will log both compact and multiline.",
96
+ 'both will log both compact and multiline.',
97
97
  ),
98
98
  cfg.IntOpt(
99
- "default_packet_send_count",
99
+ 'default_packet_send_count',
100
100
  default=3,
101
- help="The number of times to send a non ack packet before giving up.",
101
+ help='The number of times to send a non ack packet before giving up.',
102
102
  ),
103
103
  cfg.IntOpt(
104
- "default_ack_send_count",
104
+ 'default_ack_send_count',
105
105
  default=3,
106
- help="The number of times to send an ack packet in response to recieving a packet.",
106
+ help='The number of times to send an ack packet in response to recieving a packet.',
107
107
  ),
108
108
  cfg.IntOpt(
109
- "packet_list_maxlen",
109
+ 'packet_list_maxlen',
110
110
  default=100,
111
- help="The maximum number of packets to store in the packet list.",
111
+ help='The maximum number of packets to store in the packet list.',
112
112
  ),
113
113
  cfg.IntOpt(
114
- "packet_list_stats_maxlen",
114
+ 'packet_list_stats_maxlen',
115
115
  default=20,
116
- help="The maximum number of packets to send in the stats dict for admin ui.",
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
- "enable_seen_list",
119
+ 'enable_seen_list',
120
120
  default=True,
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.",
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
- "enable_packet_logging",
125
+ 'enable_packet_logging',
126
126
  default=True,
127
- help="Set this to False, to disable logging of packets to the log file.",
127
+ help='Set this to False, to disable logging of packets to the log file.',
128
128
  ),
129
129
  cfg.BoolOpt(
130
- "load_help_plugin",
130
+ 'load_help_plugin',
131
131
  default=True,
132
- help="Set this to False to disable the help plugin.",
132
+ help='Set this to False to disable the help plugin.',
133
133
  ),
134
134
  cfg.BoolOpt(
135
- "enable_sending_ack_packets",
135
+ 'enable_sending_ack_packets',
136
136
  default=True,
137
- help="Set this to False, to disable sending of ack packets. This will entirely stop"
138
- "APRSD from sending ack packets.",
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
- "enabled",
144
+ 'enabled',
145
145
  default=False,
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",
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
- "callsigns",
152
- help="Callsigns to watch for messsages",
151
+ 'callsigns',
152
+ help='Callsigns to watch for messsages',
153
153
  ),
154
154
  cfg.StrOpt(
155
- "alert_callsign",
156
- help="The Ham Callsign to send messages to for watch list alerts.",
155
+ 'alert_callsign',
156
+ help='The Ham Callsign to send messages to for watch list alerts.',
157
157
  ),
158
158
  cfg.IntOpt(
159
- "packet_keep_count",
159
+ 'packet_keep_count',
160
160
  default=10,
161
- help="The number of packets to store.",
161
+ help='The number of packets to store.',
162
162
  ),
163
163
  cfg.IntOpt(
164
- "alert_time_seconds",
164
+ 'alert_time_seconds',
165
165
  default=3600,
166
- help="Time to wait before alert is sent on new message for "
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
- "enabled_plugins",
173
+ 'enabled_plugins',
175
174
  default=[
176
- "aprsd.plugins.fortune.FortunePlugin",
177
- "aprsd.plugins.location.LocationPlugin",
178
- "aprsd.plugins.ping.PingPlugin",
179
- "aprsd.plugins.time.TimePlugin",
180
- "aprsd.plugins.weather.OWMWeatherPlugin",
181
- "aprsd.plugins.version.VersionPlugin",
182
- "aprsd.plugins.notify.NotifySeenPlugin",
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="Comma separated list of enabled plugins for APRSD."
185
- "To enable installed external plugins add them here."
186
- "The full python path to the class name must be used",
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
- "enabled",
191
+ 'enabled',
193
192
  default=False,
194
- help="Enable sending aprs registry information. This will let the "
193
+ help='Enable sending aprs registry information. This will let the '
195
194
  "APRS registry know about your service and it's uptime. "
196
- "No personal information is sent, just the callsign, uptime and description. "
197
- "The service callsign is the callsign set in [DEFAULT] section.",
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
- "description",
199
+ 'description',
201
200
  default=None,
202
- help="Description of the service to send to the APRS registry. "
203
- "This is what will show up in the APRS registry."
204
- "If not set, the description will be the same as the callsign.",
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
- "registry_url",
208
- default="https://aprs.hemna.com/api/v1/registry",
209
- help="The APRS registry domain name to send the information to.",
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
- "service_website",
211
+ 'service_website',
213
212
  default=None,
214
- help="The website for your APRS service to send to the APRS registry.",
213
+ help='The website for your APRS service to send to the APRS registry.',
215
214
  ),
216
215
  cfg.IntOpt(
217
- "frequency_seconds",
216
+ 'frequency_seconds',
218
217
  default=3600,
219
- help="The frequency in seconds to send the APRS registry information.",
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
- "DEFAULT": (aprsd_opts + enabled_plugins_opts),
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
- "CRITICAL": logging.CRITICAL,
11
- "ERROR": logging.ERROR,
12
- "WARNING": logging.WARNING,
13
- "INFO": logging.INFO,
14
- "DEBUG": logging.DEBUG,
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 = "%m/%d/%Y %I:%M:%S %p"
17
+ DEFAULT_DATE_FORMAT = '%m/%d/%Y %I:%M:%S %p'
18
18
  DEFAULT_LOG_FORMAT = (
19
- "[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s]"
20
- " %(message)s - [%(pathname)s:%(lineno)d]"
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
- "<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>"
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="logging",
33
- title="Logging options",
32
+ name='logging',
33
+ title='Logging options',
34
34
  )
35
35
  logging_opts = [
36
36
  cfg.StrOpt(
37
- "logfile",
37
+ 'logfile',
38
38
  default=None,
39
- help="File to log to",
39
+ help='File to log to',
40
40
  ),
41
41
  cfg.StrOpt(
42
- "logformat",
42
+ 'logformat',
43
43
  default=DEFAULT_LOG_FORMAT,
44
- help="Log file format, unless rich_logging enabled.",
44
+ help='Log file format, unless rich_logging enabled.',
45
45
  ),
46
46
  cfg.StrOpt(
47
- "log_level",
48
- default="INFO",
47
+ 'log_level',
48
+ default='INFO',
49
49
  choices=LOG_LEVELS.keys(),
50
- help="Log level for logging of events.",
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
- "aprslib",
67
- "aprslib.parsing",
68
- "aprslib.exceptions",
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
- "sink": sys.stdout,
80
- "serialize": False,
81
- "format": CONF.logging.logformat,
82
- "colorize": True,
83
- "level": log_level,
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
- "sink": CONF.logging.logfile,
90
- "serialize": False,
91
- "format": CONF.logging.logformat,
92
- "colorize": False,
93
- "level": log_level,
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("DEBUG", color="<fg #BABABA>")
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
- repr = (
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:
@@ -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