[fix](trx-server): use raw bytes for APRS position parsing
The APRS info field is raw AX.25 bytes, not valid UTF-8. from_utf8_lossy inserts multi-byte replacement characters, causing panics when the position parser sliced by byte index. Switch parse_aprs_position, parse_aprs_compressed, parse_aprs_lat, and parse_aprs_lon to operate on &[u8] instead of &str. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -384,10 +384,11 @@ fn parse_aprs(ax25: &Ax25Frame) -> AprsPacket {
|
|||||||
.map(|d| format_call(d))
|
.map(|d| format_call(d))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(",");
|
.join(",");
|
||||||
let info_str = String::from_utf8_lossy(&ax25.info).to_string();
|
let info = &ax25.info;
|
||||||
|
let info_str = String::from_utf8_lossy(info).to_string();
|
||||||
|
|
||||||
let packet_type = if !info_str.is_empty() {
|
let packet_type = if !info.is_empty() {
|
||||||
match info_str.as_bytes()[0] {
|
match info[0] {
|
||||||
b'!' | b'=' | b'/' | b'@' => "Position",
|
b'!' | b'=' | b'/' | b'@' => "Position",
|
||||||
b':' => "Message",
|
b':' => "Message",
|
||||||
b'>' => "Status",
|
b'>' => "Status",
|
||||||
@@ -407,7 +408,7 @@ fn parse_aprs(ax25: &Ax25Frame) -> AprsPacket {
|
|||||||
let mut symbol_code = None;
|
let mut symbol_code = None;
|
||||||
|
|
||||||
if packet_type == "Position" {
|
if packet_type == "Position" {
|
||||||
if let Some(pos) = parse_aprs_position(&info_str) {
|
if let Some(pos) = parse_aprs_position(info) {
|
||||||
lat = Some(pos.0);
|
lat = Some(pos.0);
|
||||||
lon = Some(pos.1);
|
lon = Some(pos.1);
|
||||||
symbol_table = Some(pos.2.to_string());
|
symbol_table = Some(pos.2.to_string());
|
||||||
@@ -429,61 +430,56 @@ fn parse_aprs(ax25: &Ax25Frame) -> AprsPacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_aprs_position(info_str: &str) -> Option<(f64, f64, char, char)> {
|
fn parse_aprs_position(info: &[u8]) -> Option<(f64, f64, char, char)> {
|
||||||
if info_str.is_empty() {
|
if info.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let bytes = info_str.as_bytes();
|
let dt = info[0];
|
||||||
let dt = bytes[0];
|
|
||||||
|
|
||||||
let pos_str = match dt {
|
let pos = match dt {
|
||||||
b'!' | b'=' => &info_str[1..],
|
b'!' | b'=' => &info[1..],
|
||||||
b'/' | b'@' => {
|
b'/' | b'@' => {
|
||||||
if info_str.len() < 9 {
|
if info.len() < 9 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
&info_str[8..]
|
&info[8..]
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if pos_str.is_empty() {
|
if pos.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let first = pos_str.as_bytes()[0];
|
if pos[0] < b'0' || pos[0] > b'9' {
|
||||||
if first < b'0' || first > b'9' {
|
return parse_aprs_compressed(pos);
|
||||||
return parse_aprs_compressed(pos_str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uncompressed: DDMM.MMN/DDDMM.MMEsYYY
|
// Uncompressed: DDMM.MMN/DDDMM.MMEsYYY
|
||||||
if pos_str.len() < 19 {
|
if pos.len() < 19 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lat_str = &pos_str[..8];
|
let sym_table = pos[8] as char;
|
||||||
let sym_table = pos_str.as_bytes()[8] as char;
|
let sym_code = pos[18] as char;
|
||||||
let lon_str = &pos_str[9..18];
|
|
||||||
let sym_code = pos_str.as_bytes()[18] as char;
|
|
||||||
|
|
||||||
let lat = parse_aprs_lat(lat_str)?;
|
let lat = parse_aprs_lat(&pos[..8])?;
|
||||||
let lon = parse_aprs_lon(lon_str)?;
|
let lon = parse_aprs_lon(&pos[9..18])?;
|
||||||
|
|
||||||
Some((lat, lon, sym_table, sym_code))
|
Some((lat, lon, sym_table, sym_code))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_aprs_compressed(pos_str: &str) -> Option<(f64, f64, char, char)> {
|
fn parse_aprs_compressed(pos: &[u8]) -> Option<(f64, f64, char, char)> {
|
||||||
if pos_str.len() < 10 {
|
if pos.len() < 10 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let bytes = pos_str.as_bytes();
|
let sym_table = pos[0] as char;
|
||||||
let sym_table = bytes[0] as char;
|
|
||||||
|
|
||||||
let mut lat_val: u32 = 0;
|
let mut lat_val: u32 = 0;
|
||||||
let mut lon_val: u32 = 0;
|
let mut lon_val: u32 = 0;
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let lc = bytes[1 + i] as i32 - 33;
|
let lc = pos[1 + i] as i32 - 33;
|
||||||
let xc = bytes[5 + i] as i32 - 33;
|
let xc = pos[5 + i] as i32 - 33;
|
||||||
if lc < 0 || lc > 90 || xc < 0 || xc > 90 {
|
if lc < 0 || lc > 90 || xc < 0 || xc > 90 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -498,22 +494,21 @@ fn parse_aprs_compressed(pos_str: &str) -> Option<(f64, f64, char, char)> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sym_code = bytes[9] as char;
|
let sym_code = pos[9] as char;
|
||||||
let lat = (lat * 1e6).round() / 1e6;
|
let lat = (lat * 1e6).round() / 1e6;
|
||||||
let lon = (lon * 1e6).round() / 1e6;
|
let lon = (lon * 1e6).round() / 1e6;
|
||||||
|
|
||||||
Some((lat, lon, sym_table, sym_code))
|
Some((lat, lon, sym_table, sym_code))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_aprs_lat(s: &str) -> Option<f64> {
|
fn parse_aprs_lat(b: &[u8]) -> Option<f64> {
|
||||||
if s.len() < 8 {
|
if b.len() < 8 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let deg: f64 = s[..2].parse().ok()?;
|
let deg: f64 = std::str::from_utf8(&b[..2]).ok()?.parse().ok()?;
|
||||||
let min: f64 = s[2..7].parse().ok()?;
|
let min: f64 = std::str::from_utf8(&b[2..7]).ok()?.parse().ok()?;
|
||||||
let ns = s.as_bytes()[7];
|
|
||||||
let mut lat = deg + min / 60.0;
|
let mut lat = deg + min / 60.0;
|
||||||
match ns {
|
match b[7] {
|
||||||
b'S' | b's' => lat = -lat,
|
b'S' | b's' => lat = -lat,
|
||||||
b'N' | b'n' => {}
|
b'N' | b'n' => {}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
@@ -521,15 +516,14 @@ fn parse_aprs_lat(s: &str) -> Option<f64> {
|
|||||||
Some((lat * 1e6).round() / 1e6)
|
Some((lat * 1e6).round() / 1e6)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_aprs_lon(s: &str) -> Option<f64> {
|
fn parse_aprs_lon(b: &[u8]) -> Option<f64> {
|
||||||
if s.len() < 9 {
|
if b.len() < 9 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let deg: f64 = s[..3].parse().ok()?;
|
let deg: f64 = std::str::from_utf8(&b[..3]).ok()?.parse().ok()?;
|
||||||
let min: f64 = s[3..8].parse().ok()?;
|
let min: f64 = std::str::from_utf8(&b[3..8]).ok()?.parse().ok()?;
|
||||||
let ew = s.as_bytes()[8];
|
|
||||||
let mut lon = deg + min / 60.0;
|
let mut lon = deg + min / 60.0;
|
||||||
match ew {
|
match b[8] {
|
||||||
b'W' | b'w' => lon = -lon,
|
b'W' | b'w' => lon = -lon,
|
||||||
b'E' | b'e' => {}
|
b'E' | b'e' => {}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
|
|||||||
Reference in New Issue
Block a user