Skip to content

Commit 046ec85

Browse files
committed
Updated code to resolve most of the review comments
1 parent b6c1cda commit 046ec85

File tree

8 files changed

+302
-56
lines changed

8 files changed

+302
-56
lines changed

\

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
from binascii import crc_hqx
2+
3+
from ax25 import Address, Control, Frame, FrameType
4+
from pyStuffing import BitStuffing
5+
6+
7+
class AX25:
8+
"""
9+
A class with the encode and decode methods for the Ax25 Protocol
10+
"""
11+
12+
_DEFAULT_SSID: int = 0
13+
_CRC_LENGTH_BITS = 16
14+
15+
def __init__(self, src: str, dst: str) -> None:
16+
"""
17+
:param src: Source Address Callsign (Should be a maximum of 6 characters long)
18+
:param dst: Destination Address Callsign (Should be a maximum of 6 characters long)
19+
"""
20+
if Address.valid_call(src) and Address.valid_call(dst):
21+
self.src_callsign = src
22+
self.dst_callsign = dst
23+
elif not Address.valid_call(src) and not Address.valid_call(dst):
24+
raise ValueError("Both Addresses are not valid")
25+
elif not Address.valid_call(src):
26+
raise ValueError("Source Address is not valid")
27+
elif not Address.valid_call(dst):
28+
raise ValueError("Destination Address is not valid")
29+
30+
def encode_frame(
31+
self,
32+
data_to_send: bytes,
33+
frame_type: FrameType,
34+
sequence_number: int = 0,
35+
) -> bytes:
36+
"""
37+
Encodes and Information Frame with the requested data using the ax25 library
38+
Note: The source and destination call signs passed in the constructor of the class are used
39+
40+
:param data_to_send: Data that needs to be sent in the frame
41+
:param ns: Send Sequence Number
42+
:return: Generated Frame
43+
"""
44+
45+
# Generate Frame Object as per Library Specfications
46+
control_block = Control(frame_type, poll_final=False, send_seqno=sequence_number)
47+
src_address = Address(call=self.src_callsign, ssid=self._DEFAULT_SSID)
48+
dst_address = Address(call=self.dst_callsign, ssid=self._DEFAULT_SSID)
49+
frame_bytes = bytearray(
50+
Frame(
51+
dst=dst_address,
52+
src=src_address,
53+
via=None,
54+
control=control_block,
55+
pid=0,
56+
data=data_to_send,
57+
).pack()
58+
)
59+
60+
# Calculate fcs using CRC 16 and then reverse it
61+
initial_crc_value = 0
62+
binary = bin(crc_hqx(frame_bytes, initial_crc_value))
63+
reverse = binary[-1:1:-1]
64+
reverse = reverse + (self._CRC_LENGTH_BITS - len(reverse)) * "0"
65+
fcs = bytearray(int(reverse, self._CRC_LENGTH_BITS // 8).to_bytes(self._CRC_LENGTH_BITS // 8, "big"))
66+
67+
frame_bytes = frame_bytes + fcs
68+
69+
# Define the flags
70+
start_end_flag = bytearray(bytes.fromhex("7E"))
71+
72+
# Use the mutability of bytearrays to append everything into a huge bytearray that contains what we want to send
73+
frame_bytes = start_end_flag + frame_bytes + start_end_flag
74+
75+
# Convert the bytearray to bytes
76+
return_frame = bytes(frame_bytes)
77+
78+
return return_frame
79+
80+
def decode_frame(self, input_data: bytes) -> Frame:
81+
"""
82+
Decodes frames passed in as bytes using the ax25 library.
83+
84+
:param input_data: Unstuffed frame in bytes
85+
:return: The decoded frame
86+
"""
87+
data = input_data[1:-1]
88+
89+
# Get the FCS flags from the original data transmission
90+
fcs_original = int.from_bytes(data[-2:], byteorder="big", signed=False)
91+
# Remove the fcs flags
92+
data = data[:-2]
93+
94+
# Calculate fcs of recieved frame and then reversing it
95+
binary = bin(crc_hqx(data, 0))
96+
reverse = binary[-1:1:-1]
97+
reverse = reverse + (self._CRC_LENGTH_BITS - len(reverse)) * "0"
98+
fcs_data = int(reverse, self._CRC_LENGTH_BITS // 8)
99+
100+
if fcs_original != fcs_data:
101+
raise ValueError("Check sums do not match")
102+
103+
return Frame.unpack(data)
104+
105+
def unstuff(self, input_data: bytes) -> bytes:
106+
"""
107+
Unstuffs frames passed in as bytes using the pyStuffing library.
108+
109+
:param input_data: Stuffed frame to be unstuffed as bytes
110+
:return: The unStuffed frame
111+
"""
112+
data = input_data[1:-1]
113+
114+
byte_list = []
115+
for byte in data:
116+
bits = bin(byte).removeprefix("0b")
117+
byte_list.append(("0" * (8 - len(bits))) + bits)
118+
byte_string = "".join(byte_list)
119+
bin_list = [int(s) for s in byte_string]
120+
121+
unstuff = BitStuffing(bin_list)
122+
unstuff.stuffed = bin_list
123+
unstuff.startUnstuffing()
124+
res = "".join([str(s) for s in unstuff.unStuffed])
125+
res = res + ("0" * (8 - len(res) % 8))
126+
data = bytes(int(res[i : i + 8], 2) for i in range(0, len(res), 8))
127+
128+
# Remove a 0 at the end of the string that might have been created as a result of adding in 0s
129+
if data[-1] == 0:
130+
data = data[:-1]
131+
132+
data_bytes = bytearray(data)
133+
start_end_flag = bytearray(bytes.fromhex("7E"))
134+
return bytes(start_end_flag + data_bytes + start_end_flag)
135+
136+
def stuff(self, input_data: bytes) -> bytes:
137+
"""
138+
Stuffs frames passed in as bytes using the pyStuffing library.
139+
140+
:param input_data: Unstuffed frame to be stuffed as bytes
141+
:return: The stuffed frame
142+
"""
143+
data_stripped = input_data[1:-1]
144+
byte_list = []
145+
for byte in data_stripped:
146+
bits = bin(byte).removeprefix("0b")
147+
byte_list.append(("0" * (8 - len(bits))) + bits)
148+
byte_string = "".join(byte_list)
149+
bin_list = [int(s) for s in byte_string]
150+
151+
unstuff = BitStuffing(bin_list)
152+
unstuff.startStuffing()
153+
res = "".join([str(s) for s in unstuff.stuffed])
154+
res = res + ("0" * (8 - len(res) % 8))
155+
data_bytes = bytearray(bytes(int(res[i : i + 8], 2) for i in range(0, len(res), 8)))
156+
157+
# Define the flags
158+
start_end_flag = bytearray(bytes.fromhex("7E"))
159+
160+
return bytes(start_end_flag + data_bytes + start_end_flag)
161+
162+
def _bytes_to_int_list(self, data: bytes) -> list[int]:
163+
"""
164+
A Function that converts bytes to a list of integers representing bits
165+
166+
:param data: The data that needs to be converted
167+
:return: A list of integers representing bits
168+
"""
169+
return []
170+
171+
def _int_list_to_bytes(self, data: list[int]):
172+
"""
173+
A Function that converts a list of integers to bytes
174+
175+
:param data: The list of integers that needs to be converted
176+
:return: The converted bytes
177+
"""
178+
179+
180+
# Example Usage
181+
if __name__ == "__main__":
182+
comm_1 = AX25("ATLAS", "AKITO")
183+
send_frame = comm_1.encode_frame(b"UW Orbital", FrameType.I, 0)
184+
print(send_frame)
185+
186+
rcv_frame = comm_1.decode_frame(send_frame)
187+
print("Source: " + str(rcv_frame.src))
188+
print("Destination: " + str(rcv_frame.dst))
189+
print("Frame Type: " + str(rcv_frame.control.frame_type))
190+
print("Data: " + str(rcv_frame.data.decode("UTF-8")))
191+
print("")
192+
193+
comm_2 = AX25("LEAFS", "CANUCK")
194+
send_frame = comm_2.encode_frame(b"Leafs in four", FrameType.U, 0)
195+
print(send_frame)
196+
197+
rcv_frame = comm_2.decode_frame(send_frame)
198+
print("Source: " + str(rcv_frame.src))
199+
print("Destination: " + str(rcv_frame.dst))
200+
print("Frame Type: " + str(rcv_frame.control.frame_type))
201+
print("Data: " + str(rcv_frame.data.decode("UTF-8")))

interfaces/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ if(${CMAKE_BUILD_TYPE} MATCHES GS)
3838
endif()
3939

4040

41-
add_library(${OBC_GS_INTERFACE_LIB_NAME} SHARED ${SOURCES})
41+
if(${CMAKE_BUILD_TYPE} MATCHES GS)
42+
add_library(${OBC_GS_INTERFACE_LIB_NAME} SHARED ${SOURCES})
43+
else()
44+
add_library(${OBC_GS_INTERFACE_LIB_NAME} STATIC ${SOURCES})
45+
endif()
46+
4247
target_include_directories(${OBC_GS_INTERFACE_LIB_NAME} PUBLIC ${INCLUDE_DIRS})
4348

4449
target_link_libraries(${OBC_GS_INTERFACE_LIB_NAME} PRIVATE

interfaces/obc_gs_interface/aes128/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ def decrypt(self, data: bytes) -> bytes:
3939
# Example
4040
if __name__ == "__main__":
4141
codec = AES128(bytes.fromhex("11223344556677889900AABBCCDDEEFF"), bytes.fromhex("11223344556677889900AABBCCDDEEFF"))
42-
data = codec.decrypt(b"Hallo")
42+
data = codec.encrypt(b"UW Orbital")
4343
print(codec.decrypt(data))

interfaces/obc_gs_interface/ax25/__init__.py

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,22 @@ class AX25:
1010
"""
1111

1212
_DEFAULT_SSID: int = 0
13+
_CRC_LENGTH_BITS = 16
1314

1415
def __init__(self, src: str, dst: str) -> None:
16+
"""
17+
:param src: Source Address Callsign (Should be a maximum of 6 characters long)
18+
:param dst: Destination Address Callsign (Should be a maximum of 6 characters long)
19+
"""
1520
if Address.valid_call(src) and Address.valid_call(dst):
1621
self.src_callsign = src
1722
self.dst_callsign = dst
23+
elif not Address.valid_call(src) and not Address.valid_call(dst):
24+
raise ValueError("Both Addresses are not valid")
25+
elif not Address.valid_call(src):
26+
raise ValueError("Source Address is not valid")
27+
elif not Address.valid_call(dst):
28+
raise ValueError("Destination Address is not valid")
1829

1930
def encode_frame(
2031
self,
@@ -47,10 +58,11 @@ def encode_frame(
4758
)
4859

4960
# Calculate fcs using CRC 16 and then reverse it
50-
binary = bin(crc_hqx(frame_bytes, 0))
61+
initial_crc_value = 0
62+
binary = bin(crc_hqx(frame_bytes, initial_crc_value))
5163
reverse = binary[-1:1:-1]
52-
reverse = reverse + (16 - len(reverse)) * "0"
53-
fcs = bytearray(int(reverse, 2).to_bytes(2, "big"))
64+
reverse = reverse + (self._CRC_LENGTH_BITS - len(reverse)) * "0"
65+
fcs = bytearray(int(reverse, self._CRC_LENGTH_BITS // 8).to_bytes(self._CRC_LENGTH_BITS // 8, "big"))
5466

5567
frame_bytes = frame_bytes + fcs
5668

@@ -82,8 +94,8 @@ def decode_frame(self, input_data: bytes) -> Frame:
8294
# Calculate fcs of recieved frame and then reversing it
8395
binary = bin(crc_hqx(data, 0))
8496
reverse = binary[-1:1:-1]
85-
reverse = reverse + (16 - len(reverse)) * "0"
86-
fcs_data = int(reverse, 2)
97+
reverse = reverse + (self._CRC_LENGTH_BITS - len(reverse)) * "0"
98+
fcs_data = int(reverse, self._CRC_LENGTH_BITS // 8)
8799

88100
if fcs_original != fcs_data:
89101
raise ValueError("Check sums do not match")
@@ -99,19 +111,12 @@ def unstuff(self, input_data: bytes) -> bytes:
99111
"""
100112
data = input_data[1:-1]
101113

102-
byte_list = []
103-
for byte in data:
104-
bits = bin(byte).removeprefix("0b")
105-
byte_list.append(("0" * (8 - len(bits))) + bits)
106-
byte_string = "".join(byte_list)
107-
bin_list = [int(s) for s in byte_string]
114+
int_list = self._bytes_to_int_list(data)
108115

109-
unstuff = BitStuffing(bin_list)
110-
unstuff.stuffed = bin_list
116+
unstuff = BitStuffing(int_list)
117+
unstuff.stuffed = int_list
111118
unstuff.startUnstuffing()
112-
res = "".join([str(s) for s in unstuff.unStuffed])
113-
res = res + ("0" * (8 - len(res) % 8))
114-
data = bytes(int(res[i : i + 8], 2) for i in range(0, len(res), 8))
119+
data = self._int_list_to_bytes(unstuff.unStuffed)
115120

116121
# Remove a 0 at the end of the string that might have been created as a result of adding in 0s
117122
if data[-1] == 0:
@@ -129,24 +134,47 @@ def stuff(self, input_data: bytes) -> bytes:
129134
:return: The stuffed frame
130135
"""
131136
data_stripped = input_data[1:-1]
132-
byte_list = []
133-
for byte in data_stripped:
134-
bits = bin(byte).removeprefix("0b")
135-
byte_list.append(("0" * (8 - len(bits))) + bits)
136-
byte_string = "".join(byte_list)
137-
bin_list = [int(s) for s in byte_string]
138137

139-
unstuff = BitStuffing(bin_list)
138+
unstuff = BitStuffing(self._bytes_to_int_list(data_stripped))
140139
unstuff.startStuffing()
141-
res = "".join([str(s) for s in unstuff.stuffed])
142-
res = res + ("0" * (8 - len(res) % 8))
143-
data_bytes = bytearray(bytes(int(res[i : i + 8], 2) for i in range(0, len(res), 8)))
140+
data_bytes = bytearray(self._int_list_to_bytes(unstuff.stuffed))
144141

145142
# Define the flags
146143
start_end_flag = bytearray(bytes.fromhex("7E"))
147144

148145
return bytes(start_end_flag + data_bytes + start_end_flag)
149146

147+
def _bytes_to_int_list(self, data: bytes) -> list[int]:
148+
"""
149+
A Function that converts bytes to a list of integers representing bits
150+
151+
:param data: The data that needs to be converted
152+
:return: A list of integers representing bits of the data parameter
153+
"""
154+
byte_list = []
155+
for byte in data:
156+
bits = bin(byte).removeprefix("0b")
157+
byte_list.append(("0" * (8 - len(bits))) + bits)
158+
byte_string = "".join(byte_list)
159+
int_list = [int(s) for s in byte_string]
160+
return int_list
161+
162+
def _int_list_to_bytes(self, data: list[int]) -> bytes:
163+
"""
164+
A Function that converts a list of integers to bytes
165+
166+
:param data: A list of integers representing bits that need to be converted to bytes
167+
:return: The bytes that the data parameter was converted to
168+
"""
169+
# Join all the integers into a string
170+
res = "".join([str(s) for s in data])
171+
# Make sure that this string is a multiple of 8 by adding 0s at the end (this makes sure that the last byte was
172+
# not cut off)
173+
res = res + ("0" * (8 - len(res) % 8))
174+
# Loop through every 8 characters and convert the bits to an integer which we can then convert to a byte
175+
data_bytes = bytes(int(res[i : i + 8], 2) for i in range(0, len(res), 8))
176+
return data_bytes
177+
150178

151179
# Example Usage
152180
if __name__ == "__main__":

0 commit comments

Comments
 (0)