Skip to content

Commit 1736ce9

Browse files
feature: IL2P+CRC
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 69e5bd4 commit 1736ce9

9 files changed

Lines changed: 401 additions & 6 deletions

File tree

src/audio.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ type achan_param_s struct {
208208

209209
il2p_invert_polarity int // 1 means invert on transmit. Receive handles either automatically.
210210

211+
il2p_crc int // 1 to append trailing CRC after IL2P frame, 0 to disable. Default 1.
212+
211213
v26_alternative v26_e
212214

213215
// Original implementation used alternative A for 2400 bbps PSK.

src/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,7 @@ func config_init(fname string, p_audio_config *audio_s,
874874
p_audio_config.achan[channel].layer2_xmit = LAYER2_AX25
875875
p_audio_config.achan[channel].il2p_max_fec = 1
876876
p_audio_config.achan[channel].il2p_invert_polarity = 0
877+
p_audio_config.achan[channel].il2p_crc = 1
877878

878879
p_audio_config.achan[channel].fix_bits = DEFAULT_FIX_BITS
879880
p_audio_config.achan[channel].sanity_test = SANITY_APRS
@@ -2643,6 +2644,7 @@ func config_init(fname string, p_audio_config *audio_s,
26432644
p_audio_config.achan[channel].layer2_xmit = LAYER2_IL2P
26442645
p_audio_config.achan[channel].il2p_max_fec = 1
26452646
p_audio_config.achan[channel].il2p_invert_polarity = 0
2647+
p_audio_config.achan[channel].il2p_crc = 1
26462648

26472649
for {
26482650
t = split("", false)
@@ -2660,6 +2662,10 @@ func config_init(fname string, p_audio_config *audio_s,
26602662
p_audio_config.achan[channel].il2p_max_fec = 0
26612663
case '1':
26622664
p_audio_config.achan[channel].il2p_max_fec = 1
2665+
case 'C':
2666+
p_audio_config.achan[channel].il2p_crc = 1
2667+
case 'c':
2668+
p_audio_config.achan[channel].il2p_crc = 0
26632669
default:
26642670
text_color_set(DW_COLOR_ERROR)
26652671
dw_printf("Line %d: Invalid parameter '%c' for IL2PTX command.\n", line, c)

src/il2p.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ const IL2P_MAX_PAYLOAD_BLOCKS = 5
1313
const IL2P_MAX_PARITY_SYMBOLS = 16 // For payload only.
1414
const IL2P_MAX_ENCODED_PAYLOAD_SIZE = (IL2P_MAX_PAYLOAD_SIZE + IL2P_MAX_PAYLOAD_BLOCKS*IL2P_MAX_PARITY_SYMBOLS)
1515

16-
const IL2P_MAX_PACKET_SIZE = (IL2P_SYNC_WORD_SIZE + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY + IL2P_MAX_ENCODED_PAYLOAD_SIZE)
16+
const IL2P_CRC_ENCODED_SIZE = 4 // 16-bit CRC → 4 Hamming-encoded bytes
17+
18+
const IL2P_MAX_PACKET_SIZE = (IL2P_SYNC_WORD_SIZE + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY + IL2P_MAX_ENCODED_PAYLOAD_SIZE + IL2P_CRC_ENCODED_SIZE)

src/il2p_codec.go

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ import (
4141
*
4242
*--------------------------------------------------------------*/
4343

44-
func il2p_encode_frame(pp *packet_t, max_fec int) ([]byte, int) {
44+
func il2p_encode_frame(pp *packet_t, max_fec int, crc ...bool) ([]byte, int) {
45+
46+
var appendCRC = len(crc) > 0 && crc[0]
47+
4548
// Can a type 1 header be used?
4649
var hdr, e = il2p_type_1_header(pp, max_fec)
4750

@@ -56,6 +59,10 @@ func il2p_encode_frame(pp *packet_t, max_fec int) ([]byte, int) {
5659

5760
if e == 0 {
5861
// Success. No info part.
62+
if appendCRC {
63+
var crcBytes = il2p_crc_encode(il2p_crc_calc(ax25_get_frame_data(pp)))
64+
outbuf.Write(crcBytes[:])
65+
}
5966
return outbuf.Bytes(), outbuf.Len()
6067
}
6168

@@ -66,6 +73,11 @@ func il2p_encode_frame(pp *packet_t, max_fec int) ([]byte, int) {
6673
if k > 0 {
6774
outbuf.Write(encodedPayload)
6875

76+
if appendCRC {
77+
var crcBytes = il2p_crc_encode(il2p_crc_calc(ax25_get_frame_data(pp)))
78+
outbuf.Write(crcBytes[:])
79+
}
80+
6981
// Success. Info part was <= 1023 bytes.
7082
return outbuf.Bytes(), outbuf.Len()
7183
}
@@ -93,6 +105,11 @@ func il2p_encode_frame(pp *packet_t, max_fec int) ([]byte, int) {
93105
if k > 0 {
94106
outbuf.Write(encodedPayload)
95107

108+
if appendCRC {
109+
var crcBytes = il2p_crc_encode(il2p_crc_calc(frame_data))
110+
outbuf.Write(crcBytes[:])
111+
}
112+
96113
// Success. Entire AX.25 frame <= 1023 bytes.
97114
return outbuf.Bytes(), outbuf.Len()
98115
}
@@ -129,11 +146,39 @@ func il2p_encode_frame(pp *packet_t, max_fec int) ([]byte, int) {
129146
*--------------------------------------------------------------*/
130147

131148
func il2p_decode_frame(irec []byte) *packet_t {
132-
var uhdr, e = il2p_clarify_header(irec)
149+
var uhdr, e = il2p_clarify_header(irec[:IL2P_HEADER_SIZE+IL2P_HEADER_PARITY])
133150

134151
// TODO?: for symmetry we might want to clarify the payload before combining.
135152

136-
return il2p_decode_header_payload(uhdr, irec[IL2P_HEADER_SIZE+IL2P_HEADER_PARITY:], &e)
153+
var payload = irec[IL2P_HEADER_SIZE+IL2P_HEADER_PARITY:]
154+
155+
// Determine if trailing CRC is present by computing the encoded payload size
156+
// and checking if there are extra bytes beyond it.
157+
var _, max_fec, payload_len = il2p_get_header_attributes(uhdr)
158+
var _, encoded_payload_size = il2p_payload_compute(payload_len, max_fec)
159+
160+
var crc_bytes []byte
161+
if len(payload) >= encoded_payload_size+IL2P_CRC_ENCODED_SIZE {
162+
crc_bytes = payload[encoded_payload_size : encoded_payload_size+IL2P_CRC_ENCODED_SIZE]
163+
payload = payload[:encoded_payload_size]
164+
}
165+
166+
var pp = il2p_decode_header_payload(uhdr, payload, &e)
167+
168+
// Validate CRC if present.
169+
if pp != nil && crc_bytes != nil {
170+
var frame_data = ax25_get_frame_data(pp)
171+
if !il2p_crc_check(frame_data, crc_bytes) {
172+
if il2p_get_debug() >= 1 {
173+
text_color_set(DW_COLOR_ERROR)
174+
dw_printf("IL2P trailing CRC mismatch.\n")
175+
}
176+
ax25_delete(pp)
177+
return nil
178+
}
179+
}
180+
181+
return pp
137182
}
138183

139184
/*-------------------------------------------------------------

src/il2p_crc.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package direwolf
2+
3+
/*-------------------------------------------------------------
4+
*
5+
* Purpose: IL2P Trailing CRC-16-CCITT protected by (7,4) Hamming encoding.
6+
*
7+
* The CRC provides a final validity check after RS FEC decoding,
8+
* catching rare cases where RS decoding silently produces
9+
* incorrect data under extreme error conditions.
10+
*
11+
* Reference: IL2P specification v0.6
12+
*
13+
*--------------------------------------------------------------*/
14+
15+
// Hamming (7,4) encode table from the IL2P spec.
16+
// Maps 4-bit data nibble to 7-bit Hamming codeword.
17+
var il2p_hamming_encode = [16]byte{
18+
0x00, 0x71, 0x62, 0x13, 0x54, 0x25, 0x36, 0x47,
19+
0x38, 0x49, 0x5a, 0x2b, 0x6c, 0x1d, 0x0e, 0x7f,
20+
}
21+
22+
// Hamming (7,4) decode table from the IL2P spec.
23+
// Maps 7-bit received codeword to 4-bit data nibble.
24+
// Provides single-bit error correction.
25+
var il2p_hamming_decode = [128]byte{
26+
0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x0e, 0x07,
27+
0x00, 0x09, 0x0e, 0x0b, 0x0e, 0x0d, 0x0e, 0x0e,
28+
0x00, 0x03, 0x03, 0x03, 0x04, 0x0d, 0x06, 0x03,
29+
0x08, 0x0d, 0x0a, 0x03, 0x0d, 0x0d, 0x0e, 0x0d,
30+
0x00, 0x05, 0x02, 0x0b, 0x05, 0x05, 0x06, 0x05,
31+
0x08, 0x0b, 0x0b, 0x0b, 0x0c, 0x05, 0x0e, 0x0b,
32+
0x08, 0x01, 0x06, 0x03, 0x06, 0x05, 0x06, 0x06,
33+
0x08, 0x08, 0x08, 0x0b, 0x08, 0x0d, 0x06, 0x0f,
34+
0x00, 0x09, 0x02, 0x07, 0x04, 0x07, 0x07, 0x07,
35+
0x09, 0x09, 0x0a, 0x09, 0x0c, 0x09, 0x0e, 0x07,
36+
0x04, 0x01, 0x0a, 0x03, 0x04, 0x04, 0x04, 0x07,
37+
0x0a, 0x09, 0x0a, 0x0a, 0x04, 0x0d, 0x0a, 0x0f,
38+
0x02, 0x01, 0x02, 0x02, 0x0c, 0x05, 0x02, 0x07,
39+
0x0c, 0x09, 0x02, 0x0b, 0x0c, 0x0c, 0x0c, 0x0f,
40+
0x01, 0x01, 0x02, 0x01, 0x04, 0x01, 0x06, 0x0f,
41+
0x08, 0x01, 0x0a, 0x0f, 0x0c, 0x0f, 0x0f, 0x0f,
42+
}
43+
44+
/*-------------------------------------------------------------
45+
*
46+
* Name: il2p_crc_calc
47+
*
48+
* Purpose: Compute CRC-16-CCITT over AX.25 frame data.
49+
*
50+
* Inputs: data - AX.25 frame bytes (without AX.25 FCS).
51+
*
52+
* Returns: 16-bit CRC value.
53+
*
54+
*--------------------------------------------------------------*/
55+
56+
func il2p_crc_calc(data []byte) uint16 {
57+
return fcs_calc(data)
58+
}
59+
60+
/*-------------------------------------------------------------
61+
*
62+
* Name: il2p_crc_encode
63+
*
64+
* Purpose: Hamming-encode a 16-bit CRC into 4 bytes.
65+
*
66+
* Inputs: crc - 16-bit CRC value.
67+
*
68+
* Returns: 4 bytes, each containing a Hamming (7,4) encoded nibble.
69+
* High nibble of CRC is encoded first.
70+
*
71+
*--------------------------------------------------------------*/
72+
73+
func il2p_crc_encode(crc uint16) [IL2P_CRC_ENCODED_SIZE]byte {
74+
var encoded [IL2P_CRC_ENCODED_SIZE]byte
75+
encoded[0] = il2p_hamming_encode[(crc>>12)&0x0f]
76+
encoded[1] = il2p_hamming_encode[(crc>>8)&0x0f]
77+
encoded[2] = il2p_hamming_encode[(crc>>4)&0x0f]
78+
encoded[3] = il2p_hamming_encode[crc&0x0f]
79+
return encoded
80+
}
81+
82+
/*-------------------------------------------------------------
83+
*
84+
* Name: il2p_crc_decode
85+
*
86+
* Purpose: Decode 4 Hamming-encoded bytes back to a 16-bit CRC.
87+
*
88+
* Inputs: encoded - 4 bytes of Hamming (7,4) encoded CRC.
89+
*
90+
* Returns: 16-bit CRC value.
91+
*
92+
*--------------------------------------------------------------*/
93+
94+
func il2p_crc_decode(encoded []byte) uint16 {
95+
var n0 = uint16(il2p_hamming_decode[encoded[0]&0x7f])
96+
var n1 = uint16(il2p_hamming_decode[encoded[1]&0x7f])
97+
var n2 = uint16(il2p_hamming_decode[encoded[2]&0x7f])
98+
var n3 = uint16(il2p_hamming_decode[encoded[3]&0x7f])
99+
return (n0 << 12) | (n1 << 8) | (n2 << 4) | n3
100+
}
101+
102+
/*-------------------------------------------------------------
103+
*
104+
* Name: il2p_crc_check
105+
*
106+
* Purpose: Validate received Hamming-encoded CRC against AX.25 frame data.
107+
*
108+
* Inputs: frame_data - Decoded AX.25 frame bytes.
109+
* encoded_crc - 4 bytes of received Hamming-encoded CRC.
110+
*
111+
* Returns: true if CRC matches, false otherwise.
112+
*
113+
*--------------------------------------------------------------*/
114+
115+
func il2p_crc_check(frame_data []byte, encoded_crc []byte) bool {
116+
var expected = il2p_crc_calc(frame_data)
117+
var received = il2p_crc_decode(encoded_crc)
118+
return expected == received
119+
}
120+
121+
/*-------------------------------------------------------------
122+
*
123+
* Name: il2p_crc_enabled
124+
*
125+
* Purpose: Check if IL2P trailing CRC is enabled for a channel.
126+
*
127+
* Inputs: channel - Radio channel number.
128+
*
129+
* Returns: true if CRC is enabled, false otherwise.
130+
*
131+
*--------------------------------------------------------------*/
132+
133+
func il2p_crc_enabled(channel int) bool {
134+
if save_audio_config_p == nil {
135+
return true // Default to enabled if no config available.
136+
}
137+
return save_audio_config_p.achan[channel].il2p_crc != 0
138+
}

0 commit comments

Comments
 (0)