Skip to content

Commit 49bbb27

Browse files
committed
AX25 encoding fixes.
Github user dscp46 F4HOF discovered a potential data corruption in AX25 frame encoding and the control byte. In Python3, you can not to some bit manipulations on the str data type as that has to contain valid Unicode characters. One of the bytes types must be used instead. Github user dscp46 F4H0F discovered an additional character that needs to be Yencoded to prevent packet loss d-rats_in_mobaxterm_install.sh: Reports that newerverions of mobaxterm do not have busybox pre-installed. d_rats/agw.py: d_rats/comm.py: d_rats/wlk.py: Change to use bytes type for properly encoding binary. d_rats/yencode.py: Blacklist additional characters for D-Star and AX-25 packets changes/*: Documentation for bug fixes for News file Others: Spelling and pylint fixes found.
1 parent 54bddfd commit 49bbb27

File tree

11 files changed

+96
-71
lines changed

11 files changed

+96
-71
lines changed

changes/298.bugfix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Need to use AX.25 Unnumbered Information frames.
2+
Fix possible Filename corruption for AX.25 frames.

changes/304.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make sure busybox is installed for MobaXterm installs

changes/305.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Possible data loss if special binary sequence sent over D-Star radio links

d-rats_in_mobaxterm_install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export DEBIAN_FRONTEND
1616
$apt_get -y upgrade
1717
$apt_get -y install \
1818
aspell aspell-de aspell-en aspell-es aspell-it \
19-
ffmpeg \
19+
busybox ffmpeg \
2020
gedit gettext gettext-devel git gcc-core \
2121
libgtk3_0 libjpeg-devel libportaudio-devel \
2222
python39 python39-devel python39-gi python39-lxml \

d-rats_repeater.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@
4242
import gettext
4343
_ = gettext.gettext
4444

45-
import gi
45+
import gi # type: ignore
4646
gi.require_version("Gtk", "3.0")
47-
from gi.repository import Gtk
48-
from gi.repository import GObject
47+
from gi.repository import Gtk # type: ignore
48+
from gi.repository import GObject # type: ignore
4949

5050
# Make sure no one tries to run this with privileges.
5151
try:

d_rats/agw.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -637,30 +637,50 @@ def ssid(call):
637637
try:
638638
sid = int(sid)
639639
except ValueError:
640-
raise InvalidCallsignError("Invalid SSID `%s'" % sid)
640+
raise InvalidCallsignError("Invalid SSID `%s'" % sid) from None
641641

642642
if sid < 0 or sid > 7:
643643
raise InvalidCallsignError("Invalid SSID `%i'" % sid)
644644

645645
return callsign, sid
646646

647647

648-
def encode_ssid(sid, last=False):
648+
def encode_ssid(sid, last=False) -> bytes:
649649
'''
650650
Encode SSID for transmission.
651651
652652
:param sid: Station ID number
653653
:type sid: int
654-
:param last: last flag, unused
654+
:param last: last flag, indicates last call in a list of calls.
655655
:type last: bool
656656
:returns: Encoded Station ID character
657-
:rtype: str
657+
:rtype: bytes
658658
'''
659659
if last:
660660
mask = 0x61
661661
else:
662662
mask = 0x60
663-
return chr((sid << 1) | mask)
663+
result = (sid << 1) | mask
664+
return result.to_bytes(1,'little')
665+
666+
667+
def encode_call(callsign: str, last=False) -> bytes:
668+
'''
669+
Encode callsign to AX25 call sign field.
670+
671+
:param callsign: Call sign with SSID
672+
:type callsign: str
673+
:param last: last flag, indicates last call in a list of calls.
674+
:type last: bool
675+
:returns: Encoded call sign
676+
:rtype: bytes
677+
'''
678+
call, sid = ssid(callsign)
679+
# Encode the call by grabbing each character and shifting
680+
# left one bit
681+
ax25_call = b"".join([chr(ord(x) << 1) for x in call])
682+
ax25_call += encode_ssid(sid=sid, last=last)
683+
return ax25_call
664684

665685

666686
def transmit_data(conn, dcall, spath, data):
@@ -677,26 +697,18 @@ def transmit_data(conn, dcall, spath, data):
677697
:type data: bytes
678698
'''
679699
global_logger.info("transmit_data:")
680-
call, sid = ssid(dcall)
681700

682-
# Encode the call by grabbing each character and shifting
683-
# left one bit
684-
dst_str = "".join([chr(ord(x) << 1) for x in call])
685-
dst_str += encode_ssid(sid)
686-
dst = dst_str.encode('utf-8', 'replace')
701+
dst = encode_call(callsign=dcall)
687702

688-
src_str = ""
689703
for scall in spath:
690-
call, sid = ssid(scall)
691-
src_str += "".join([chr(ord(x) << 1) for x in call])
692-
src_str += encode_ssid(sid, spath[-1] == scall)
693-
src = src_str.encode('utf-8', 'replace')
704+
last = spath[-1] == scall
705+
src = encode_call(callsign=scall, last=last)
694706

695707
data_frame = struct.pack("!B7s%isBB" % len(src),
696708
0x00, # Space for flag (?)
697709
dst, # Dest Call
698710
src, # Source Path
699-
0x3E, # Info
711+
0x03, # Unnumbered Info
700712
0xF0) # PID: No layer 3
701713
data_frame += data
702714

@@ -747,6 +759,7 @@ def test_server(host="127.0.0.1", port=8000):
747759
'''
748760
# Quick and dirty simulator for agwpe unit tests.
749761
global_logger.info("test_server: starting %s:%i", host, port)
762+
# pylint: disable=import-outside-toplevel
750763
from time import sleep
751764

752765
agw = AGWConnection(host, port, timeout=10, server=True)
@@ -775,6 +788,7 @@ def main():
775788
datefmt="%m/%d/%Y %H:%M:%S",
776789
level=logging.INFO)
777790

791+
# pylint: disable=import-outside-toplevel
778792
from time import sleep
779793
server = threading.Thread(target=test_server)
780794
server.start()

d_rats/aprs_dprs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class AprsDprsCodes:
3535
APRS_ALTERNATE_SYMBOL_TABLE = '\\'
3636
APRS_ADVISORY_CODE = '\\<'
3737
APRS_CAR_CODE = '/>'
38-
APRS_DIGI_CODD = '/#'
38+
APRS_DIGI_CODE = '/#'
3939
APRS_DOT_CODE = '//'
4040
APRS_FALLBACK_CODE = '//'
4141
APRS_INFO_KIOSK_CODE = '\\?'
@@ -85,7 +85,7 @@ def _init_dprs_to_aprs(cls):
8585
and a list of regular APRS symbols
8686
8787
`DPRS information <https://www.aprs-is.net/DPRS.aspx>`_
88-
`DPRS Caldulator <http://www.aprs-is.net/DPRSCalc.aspx>_
88+
`DPRS calculator <http://www.aprs-is.net/DPRSCalc.aspx>_
8989
'''
9090

9191
if cls._dprs_to_aprs:

d_rats/comm.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# Needed for python2+python3 support
1414
import sys
1515

16-
import serial
16+
import serial # type: ignore
1717

1818
from . import utils
1919
from . import agw
@@ -394,15 +394,15 @@ def is_xon(self):
394394
self.dsr_seen = True
395395
time.sleep(0.01)
396396
self.state = True # Pending re-write
397-
# Code below is not optimmal, for some reason it almost works
397+
# Code below is not optimal, for some reason it almost works
398398
#
399399
# Using the serial driver xon/xoff protocol handling instead of
400400
# this has been found not work in some cases.
401401
#
402402
# Bug #1 No code is present to detect if an XOFF or XON appears in
403403
# the middle of a data stream.
404404
# Bug #2 The XOFF/XON is not guaranteed to show up at the
405-
# beginnng of a read packet.
405+
# beginning of a read packet.
406406
# Apparently the serial device is expected to send at least XON/XOFF to
407407
# back to D-rats for every packet sent to it.
408408
if self.in_waiting == 0:
@@ -947,19 +947,13 @@ def write(self, buf):
947947
:type buf: bytes
948948
'''
949949
spath = [self.__call,] + self.__path.split(",")
950-
src = ""
951950
for scall in spath:
952-
call, sid = agw.ssid(scall)
953-
src += "".join([chr(ord(x) << 1) for x in call])
954-
src += agw.encode_ssid(sid, spath[-1] == scall)
951+
last = spath[-1] == scall
952+
send_src = agw.encode_call(callsign=scall, last=last)
955953

956-
call, sid = agw.ssid("DRATS")
957-
dst = "".join([chr(ord(x) << 1) for x in call])
958-
dst += agw.encode_ssid(sid)
959-
send_dst = dst.encode('utf-8', 'replace')
960-
send_src = src.encode('utf-8', 'replace')
954+
send_dst = agw.encode_call(callsign="DRATS")
961955

962-
hdr = struct.pack("!7s%isBB" % len(src),
956+
hdr = struct.pack("!7s%isBB" % len(send_src),
963957
send_dst, # Dest call
964958
send_src, # Source path
965959
0x03, # Control

d_rats/gps.py

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
import socket
3131

3232
from math import pi, cos, acos, sin, atan2
33-
import serial
33+
import gettext
34+
import serial # type: ignore
3435

3536
from .dplatform import Platform
3637

@@ -39,11 +40,7 @@
3940
from .aprs_icons import APRSicons
4041
from .dratsexception import DPRSException
4142

42-
# This makes pylance happy with out overriding settings
43-
# from the invoker of the class
44-
if not '_' in locals():
45-
import gettext
46-
_ = gettext.gettext
43+
_ = gettext.gettext
4744

4845
TEST = "$GPGGA,180718.02,4531.3740,N,12255.4599,W,1,07,1.4," \
4946
"50.6,M,-21.4,M,,*63 KE7JSS ,440.350+ PL127.3"
@@ -213,7 +210,7 @@ def calc(buf):
213210
for _char in buf:
214211
char = ord(_char)
215212
for _indx in range(0, 8):
216-
xorflag = (((icomcrc ^ char) & 0x01) == 0x01)
213+
xorflag = ((icomcrc ^ char) & 0x01) == 0x01
217214
icomcrc = (icomcrc >> 1) & 0x7fff
218215
if xorflag:
219216
icomcrc ^= 0x8408
@@ -707,14 +704,14 @@ def to_aprs(self, dest="APRATS",
707704
'''
708705
To APRS.
709706
710-
:param dest: Destination, default "APRSATS"
707+
:param dest: Destination, default "APRATS"
711708
:param dest: str
712709
:param code: APRS code, default APRS_CAR_CODE
713710
:type code: str
714711
:returns: a GPS-A (APRS-compliant) string
715712
:rtype: str
716713
'''
717-
symtab = code[0]
714+
symbol_table = code[0]
718715
symbol = code[1]
719716
stamp = time.strftime("%H%M%S", time.gmtime())
720717

@@ -726,25 +723,25 @@ def to_aprs(self, dest="APRATS",
726723
sta_str = "%s>%s,DSTAR*:/%sh" % (sta, dest, stamp)
727724

728725
if self.latitude > 0:
729-
northsouth = "N"
726+
north_south = "N"
730727
lat_m = 1
731728
else:
732-
northsouth = "S"
729+
north_south = "S"
733730
lat_m = -1
734731

735732
if self.longitude > 0:
736-
eastwest = "E"
733+
east_west = "E"
737734
lon_m = 1
738735
else:
739-
eastwest = "W"
736+
east_west = "W"
740737
lon_m = -1
741738

742739
sta_str += "%07.2f%s%s%08.2f%s%s" % \
743740
(deg2nmea(self.latitude * lat_m),
744-
northsouth,
745-
symtab,
741+
north_south,
742+
symbol_table,
746743
deg2nmea(self.longitude * lon_m),
747-
eastwest,
744+
east_west,
748745
symbol)
749746
if self.speed and self.direction:
750747
sta_str += "%03.0f/%03.0f" % \
@@ -891,7 +888,7 @@ def __init__(self, sentence, station=None):
891888
else:
892889
self.logger.info("Unsupported GPS sentence type: %s", sentence)
893890

894-
def _test_checksum(self, string, csum):
891+
def _test_checksum(self, string, checksum):
895892
try:
896893
idx = string.index("*")
897894
except ValueError:
@@ -901,14 +898,14 @@ def _test_checksum(self, string, csum):
901898

902899
segment = string[1:idx]
903900

904-
csum = csum.upper()
905-
calc_csum = nmea_checksum(segment).upper()
901+
checksum = checksum.upper()
902+
calc_checksum = nmea_checksum(segment).upper()
906903

907-
if csum != calc_csum:
904+
if checksum != calc_checksum:
908905
self.logger.info("_test_checksum: Failed checksum: %s != %s",
909-
csum, calc_csum)
906+
checksum, calc_checksum)
910907

911-
return csum == calc_csum
908+
return checksum == calc_checksum
912909

913910
def _parse_gpgga(self, string):
914911
elements = string.split(",", 14)
@@ -934,7 +931,7 @@ def _parse_gpgga(self, string):
934931
if not match:
935932
raise GpsGppgaChecksumError("No checksum (%s)" % elements[14])
936933

937-
csum = match.group(2)
934+
checksum = match.group(2)
938935
if "," in match.group(3):
939936
sta, com = match.group(3).split(",", 1)
940937
if not sta.strip().startswith("$"):
@@ -945,7 +942,7 @@ def _parse_gpgga(self, string):
945942
if len(self.comment) >= 7 and "*" in self.comment[-3:-1]:
946943
self._parse_dprs_comment()
947944

948-
self.valid = self._test_checksum(string, csum)
945+
self.valid = self._test_checksum(string, checksum)
949946

950947
def _parse_gprmc(self, string):
951948
if "\r\n" in string:
@@ -984,7 +981,7 @@ def _parse_gprmc(self, string):
984981
self.logger.info("_parse_gprmc: Invalid end: %s", elements[end])
985982
return
986983

987-
csum = match.group(1)
984+
checksum = match.group(1)
988985
if "," in station:
989986
sta, com = station.split(",", 1)
990987
self.station = utils.filter_to_ascii(sta.strip())
@@ -1000,7 +997,7 @@ def _parse_gprmc(self, string):
1000997
elements[2])
1001998
else:
1002999
self.logger.info("_parse_gprmc: GPRMC is valid")
1003-
self.valid = self._test_checksum(string, csum)
1000+
self.valid = self._test_checksum(string, checksum)
10041001

10051002
def _from_nmea_gpgga(self, string):
10061003
string = string.replace('\r', ' ')
@@ -1378,7 +1375,7 @@ def connect(self):
13781375
:rtype: bool
13791376
'''
13801377
try:
1381-
_, host, port = self.port.split(":", 3)
1378+
_proto, host, port = self.port.split(":", 3)
13821379
port = int(port)
13831380
except ValueError as err:
13841381
self.logger.info("connect: Unable to parse %s (%s)",

d_rats/wl2k.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
import time
1212
import re
1313
import random
14-
import gi
14+
import gi # type: ignore
1515

1616
gi.require_version("Gtk", "3.0")
17-
from gi.repository import GLib
18-
from gi.repository import GObject
17+
from gi.repository import GLib # type: ignore
18+
from gi.repository import GObject # type: ignore
1919

2020
from d_rats import version
2121
from d_rats import formgui
@@ -393,8 +393,9 @@ def send_to_socket(self, sock):
393393
data = self.__lzh_content
394394

395395
# filename \0 length(0) \0
396-
header_str = self.__name + "\x00" + chr(len(data) & 0xFF) + "\x00"
397-
header = header_str.encode('utf-8', 'replace')
396+
data_len = chr(len(data) & 0xFF)
397+
header = self.__name.encode('utf-8', 'replace') + b'\x00' + \
398+
data_len.to_bytes(1,'little') + b'\x00'
398399
sock.send(struct.pack("<BB", FBB_BLOCK_HDR, len(header)) + header)
399400

400401
checksum = 0
@@ -680,7 +681,7 @@ def send_messages(self, messages):
680681
cs_octet += ord("\r")
681682
self._send(proposal)
682683

683-
cs_octet = ((~cs_octet & 0xFF) + 1)
684+
cs_octet = (~cs_octet & 0xFF) + 1
684685
data_str = "F> %02X" % cs_octet
685686
self._send(data_str.encode('utf-8', 'replace'))
686687
resp = self._recv()

0 commit comments

Comments
 (0)