1use super::*;
17
18pub(crate) fn handle(
20 bv: &BitVec,
21 _station: Station,
22 store: &mut NmeaParser,
23 own_vessel: bool,
24) -> Result<ParsedMessage, ParseError> {
25 let (part_a, part_b) = match pick_u64(bv, 38, 2) {
28 0 => (true, false),
29 1 => (false, true),
30 _ => {
31 return Err(format!(
32 "AIVDM type 24 part number has unexpected value: {}",
33 pick_u64(bv, 38, 2)
34 )
35 .into());
36 }
37 };
38
39 let vsd = VesselStaticData {
41 own_vessel,
42 ais_type: AisClass::ClassB,
43 mmsi: pick_u64(bv, 8, 30) as u32,
44 ais_version_indicator: 0,
45 imo_number: None,
46 call_sign: {
47 if part_b {
48 let raw = pick_string(bv, 90, 7);
49 match raw.as_str() {
50 "" => None,
51 _ => Some(raw),
52 }
53 } else {
54 None
55 }
56 },
57 name: {
58 if part_a {
59 let raw = pick_string(bv, 40, 120);
60 match raw.as_str() {
61 "" => None,
62 _ => Some(raw),
63 }
64 } else {
65 None
66 }
67 },
68 ship_type: {
69 if part_b {
70 ShipType::new(pick_u64(bv, 40, 8) as u8)
71 } else {
72 ShipType::NotAvailable
73 }
74 },
75 cargo_type: {
76 if part_b {
77 CargoType::new(pick_u64(bv, 40, 8) as u8)
78 } else {
79 CargoType::Undefined
80 }
81 },
82 equipment_vendor_id: {
83 if part_b {
84 Some(pick_string(bv, 48, 3))
85 } else {
86 None
87 }
88 },
89 equipment_model: {
90 if part_b {
91 Some(pick_u64(bv, 66, 4) as u8)
92 } else {
93 None
94 }
95 },
96 equipment_serial_number: {
97 if part_b {
98 Some(pick_u64(bv, 70, 20) as u32)
99 } else {
100 None
101 }
102 },
103 dimension_to_bow: {
104 if part_b {
105 Some(pick_u64(bv, 132, 9) as u16)
106 } else {
107 None
108 }
109 },
110 dimension_to_stern: {
111 if part_b {
112 Some(pick_u64(bv, 141, 9) as u16)
113 } else {
114 None
115 }
116 },
117 dimension_to_port: {
118 if part_b {
119 Some(pick_u64(bv, 150, 6) as u16)
120 } else {
121 None
122 }
123 },
124 dimension_to_starboard: {
125 if part_b {
126 Some(pick_u64(bv, 156, 6) as u16)
127 } else {
128 None
129 }
130 },
131 position_fix_type: None,
132 eta: None,
133 draught10: None,
134 destination: None,
135 mothership_mmsi: {
136 if part_b {
137 Some(pick_u64(bv, 132, 30) as u32)
138 } else {
139 None
140 }
141 },
142 };
143
144 if let Some(vsd2) = store.pull_vsd(vsd.mmsi) {
146 Ok(ParsedMessage::VesselStaticData(vsd.merge(&vsd2)?))
147 } else {
148 store.push_vsd(vsd.mmsi, vsd);
149 Ok(ParsedMessage::Incomplete)
150 }
151}
152
153impl VesselStaticData {
154 fn merge(&self, other: &VesselStaticData) -> Result<VesselStaticData, String> {
157 if self.ais_type != other.ais_type {
158 Err(format!(
159 "Mismatching AIS types: {} != {}",
160 self.ais_type, other.ais_type
161 ))
162 } else if self.mmsi != other.mmsi {
163 Err(format!(
164 "Mismatching MMSI numbers: {} != {}",
165 self.mmsi, other.mmsi
166 ))
167 } else if self.imo_number != other.imo_number {
168 Err(format!(
169 "Mismatching IMO numbers: {} != {}",
170 self.mmsi, other.mmsi
171 ))
172 } else if self.ais_version_indicator != other.ais_version_indicator {
173 Err(format!(
174 "Mismatching AIS version indicators: {} != {}",
175 self.ais_version_indicator, other.ais_version_indicator
176 ))
177 } else {
178 Ok(VesselStaticData {
179 own_vessel: self.own_vessel,
180 ais_type: self.ais_type,
181 mmsi: self.mmsi,
182 ais_version_indicator: self.ais_version_indicator,
183 imo_number: choose_some(self.imo_number, other.imo_number),
184 call_sign: choose_some_string(&self.call_sign, &other.call_sign),
185 name: choose_some_string(&self.name, &other.name),
186 ship_type: {
187 if self.ship_type != ShipType::NotAvailable {
188 self.ship_type
189 } else {
190 other.ship_type
191 }
192 },
193 cargo_type: {
194 if self.cargo_type != CargoType::Undefined {
195 self.cargo_type
196 } else {
197 other.cargo_type
198 }
199 },
200 equipment_vendor_id: choose_some_string(
201 &self.equipment_vendor_id,
202 &other.equipment_vendor_id,
203 ),
204 equipment_model: choose_some(self.equipment_model, other.equipment_model),
205 equipment_serial_number: choose_some(
206 self.equipment_serial_number,
207 other.equipment_serial_number,
208 ),
209 dimension_to_bow: choose_some(self.dimension_to_bow, other.dimension_to_bow),
210 dimension_to_stern: choose_some(self.dimension_to_stern, other.dimension_to_stern),
211 dimension_to_port: choose_some(self.dimension_to_port, other.dimension_to_port),
212 dimension_to_starboard: choose_some(
213 self.dimension_to_starboard,
214 other.dimension_to_starboard,
215 ),
216 position_fix_type: choose_some(self.position_fix_type, other.position_fix_type),
217 eta: choose_some(self.eta, other.eta),
218 draught10: choose_some(self.draught10, other.draught10),
219 destination: choose_some_string(&self.destination, &other.destination),
220 mothership_mmsi: choose_some(self.mothership_mmsi, other.mothership_mmsi),
221 })
222 }
223 }
224}
225
226fn choose_some<T>(a: Option<T>, b: Option<T>) -> Option<T> {
228 if a.is_some() {
229 a
230 } else {
231 b
232 }
233}
234
235fn choose_some_string(a: &Option<String>, b: &Option<String>) -> Option<String> {
237 if a.is_some() {
238 a.clone()
239 } else {
240 b.clone()
241 }
242}
243
244#[cfg(test)]
247mod test {
248 use super::*;
249
250 #[test]
251 fn test_parse_vdm_type24() {
252 let mut p = NmeaParser::new();
253
254 let s1 = "!AIVDM,1,1,,A,H42O55i18tMET00000000000000,2*6D";
255 match p.parse_sentence(s1) {
256 Ok(ps) => {
257 match ps {
258 ParsedMessage::VesselStaticData(_) => {
260 assert!(false);
261 return;
262 }
263 ParsedMessage::Incomplete => {
264 }
266 _ => {
267 assert!(false);
268 return;
269 }
270 }
271 }
272 Err(e) => {
273 assert_eq!(e.to_string(), "OK");
274 return;
275 }
276 }
277 let s2 = "!AIVDM,1,1,,A,H42O55lti4hhhilD3nink000?050,0*40";
278 match p.parse_sentence(s2) {
279 Ok(ps) => {
280 match ps {
281 ParsedMessage::VesselStaticData(vsd) => {
283 assert_eq!(vsd.mmsi, 271041815);
284 assert_eq!(vsd.ais_version_indicator, 0);
285 assert_eq!(vsd.imo_number, None);
286 assert_eq!(vsd.call_sign, Some("TC6163".into()));
287 assert_eq!(vsd.name, Some("PROGUY".into()));
288 assert_eq!(vsd.ship_type, ShipType::Passenger);
289 assert_eq!(vsd.cargo_type, CargoType::Undefined);
290
291 assert_eq!(vsd.equipment_vendor_id, Some("1D0".into()));
292 assert_eq!(vsd.dimension_to_bow, Some(0));
298 assert_eq!(vsd.dimension_to_stern, Some(15));
299 assert_eq!(vsd.dimension_to_port, Some(0));
300 assert_eq!(vsd.dimension_to_starboard, Some(5));
301
302 assert_eq!(vsd.position_fix_type, None);
303 assert_eq!(vsd.eta, None);
304 assert_eq!(vsd.draught10, None);
305 assert_eq!(vsd.destination, None);
306 }
307 ParsedMessage::Incomplete => {
308 assert!(false);
309 }
310 _ => {
311 assert!(false);
312 }
313 }
314 }
315 Err(e) => {
316 assert_eq!(e.to_string(), "OK");
317 }
318 }
319 }
320}