1use nodedb_types::geometry::Geometry;
18
19const WKB_POINT: u32 = 1;
21const WKB_LINESTRING: u32 = 2;
22const WKB_POLYGON: u32 = 3;
23const WKB_MULTIPOINT: u32 = 4;
24const WKB_MULTILINESTRING: u32 = 5;
25const WKB_MULTIPOLYGON: u32 = 6;
26const WKB_GEOMETRYCOLLECTION: u32 = 7;
27
28const BYTE_ORDER_LE: u8 = 1;
29
30const MAX_WKB_ELEMENTS: usize = 1_000_000;
33const MAX_WKB_BYTES: usize = 64 * 1024 * 1024;
34const MAX_WKB_DEPTH: usize = 64;
37
38pub fn geometry_to_wkb(geom: &Geometry) -> Option<Vec<u8>> {
43 let encoded_size = checked_encoded_size(geom, MAX_WKB_DEPTH)?;
44 let mut buf = Vec::with_capacity(encoded_size);
45 write_geometry(&mut buf, geom, MAX_WKB_DEPTH)?;
46 debug_assert_eq!(buf.len(), encoded_size);
47 Some(buf)
48}
49
50pub fn geometry_from_wkb(data: &[u8]) -> Option<Geometry> {
54 if data.len() > MAX_WKB_BYTES {
55 return None;
56 }
57 let mut cursor = 0;
58 read_geometry(data, &mut cursor, MAX_WKB_DEPTH)
59}
60
61pub fn wkb_bbox(data: &[u8]) -> Option<nodedb_types::BoundingBox> {
66 let geom = geometry_from_wkb(data)?;
67 Some(nodedb_types::geometry_bbox(&geom))
68}
69
70fn checked_encoded_size(geom: &Geometry, depth_remaining: usize) -> Option<usize> {
73 if depth_remaining == 0 {
74 return None;
75 }
76 let size = match geom {
77 Geometry::Point { .. } => 21,
78 Geometry::LineString { coordinates } => {
79 checked_count(coordinates.len())?;
80 bounded_add(9, coordinates.len().checked_mul(16)?)?
81 }
82 Geometry::Polygon { coordinates } => {
83 checked_count(coordinates.len())?;
84 let mut size = 9usize;
85 for ring in coordinates {
86 checked_count(ring.len())?;
87 size = bounded_add(size, bounded_add(4, ring.len().checked_mul(16)?)?)?;
88 }
89 size
90 }
91 Geometry::MultiPoint { coordinates } => {
92 checked_count(coordinates.len())?;
93 bounded_add(9, coordinates.len().checked_mul(21)?)?
94 }
95 Geometry::MultiLineString { coordinates } => {
96 if depth_remaining == 1 {
97 return None;
98 }
99 checked_count(coordinates.len())?;
100 let mut size = 9usize;
101 for line in coordinates {
102 checked_count(line.len())?;
103 size = bounded_add(size, bounded_add(9, line.len().checked_mul(16)?)?)?;
104 }
105 size
106 }
107 Geometry::MultiPolygon { coordinates } => {
108 if depth_remaining == 1 {
109 return None;
110 }
111 checked_count(coordinates.len())?;
112 let mut size = 9usize;
113 for polygon in coordinates {
114 checked_count(polygon.len())?;
115 let mut polygon_size = 9usize;
116 for ring in polygon {
117 checked_count(ring.len())?;
118 polygon_size =
119 bounded_add(polygon_size, bounded_add(4, ring.len().checked_mul(16)?)?)?;
120 }
121 size = bounded_add(size, polygon_size)?;
122 }
123 size
124 }
125 Geometry::GeometryCollection { geometries } => {
126 checked_count(geometries.len())?;
127 let mut size = 9usize;
128 for geometry in geometries {
129 size = bounded_add(size, checked_encoded_size(geometry, depth_remaining - 1)?)?;
130 }
131 size
132 }
133 _ => 9,
134 };
135 (size <= MAX_WKB_BYTES).then_some(size)
136}
137
138fn checked_count(count: usize) -> Option<()> {
139 (count <= MAX_WKB_ELEMENTS && u32::try_from(count).is_ok()).then_some(())
140}
141
142fn bounded_add(left: usize, right: usize) -> Option<usize> {
143 left.checked_add(right)
144 .filter(|&size| size <= MAX_WKB_BYTES)
145}
146
147fn write_geometry(buf: &mut Vec<u8>, geom: &Geometry, depth_remaining: usize) -> Option<()> {
148 if depth_remaining == 0 {
149 return None;
150 }
151 match geom {
152 Geometry::Point { coordinates } => {
153 write_header(buf, WKB_POINT);
154 write_f64(buf, coordinates[0]);
155 write_f64(buf, coordinates[1]);
156 }
157 Geometry::LineString { coordinates } => {
158 write_header(buf, WKB_LINESTRING);
159 write_count(buf, coordinates.len())?;
160 for c in coordinates {
161 write_f64(buf, c[0]);
162 write_f64(buf, c[1]);
163 }
164 }
165 Geometry::Polygon { coordinates } => {
166 write_header(buf, WKB_POLYGON);
167 write_count(buf, coordinates.len())?;
168 for ring in coordinates {
169 write_count(buf, ring.len())?;
170 for c in ring {
171 write_f64(buf, c[0]);
172 write_f64(buf, c[1]);
173 }
174 }
175 }
176 Geometry::MultiPoint { coordinates } => {
177 write_header(buf, WKB_MULTIPOINT);
178 write_count(buf, coordinates.len())?;
179 for c in coordinates {
180 write_header(buf, WKB_POINT);
181 write_f64(buf, c[0]);
182 write_f64(buf, c[1]);
183 }
184 }
185 Geometry::MultiLineString { coordinates } => {
186 write_header(buf, WKB_MULTILINESTRING);
187 write_count(buf, coordinates.len())?;
188 for ls in coordinates {
189 write_geometry(
190 buf,
191 &Geometry::LineString {
192 coordinates: ls.clone(),
193 },
194 depth_remaining - 1,
195 )?;
196 }
197 }
198 Geometry::MultiPolygon { coordinates } => {
199 write_header(buf, WKB_MULTIPOLYGON);
200 write_count(buf, coordinates.len())?;
201 for poly in coordinates {
202 write_geometry(
203 buf,
204 &Geometry::Polygon {
205 coordinates: poly.clone(),
206 },
207 depth_remaining - 1,
208 )?;
209 }
210 }
211 Geometry::GeometryCollection { geometries } => {
212 write_header(buf, WKB_GEOMETRYCOLLECTION);
213 write_count(buf, geometries.len())?;
214 for geometry in geometries {
215 write_geometry(buf, geometry, depth_remaining - 1)?;
216 }
217 }
218 _ => {
219 write_header(buf, WKB_GEOMETRYCOLLECTION);
220 write_u32(buf, 0);
221 }
222 }
223 Some(())
224}
225
226fn write_count(buf: &mut Vec<u8>, count: usize) -> Option<()> {
227 if count > MAX_WKB_ELEMENTS {
228 return None;
229 }
230 write_u32(buf, u32::try_from(count).ok()?);
231 Some(())
232}
233
234fn write_header(buf: &mut Vec<u8>, wkb_type: u32) {
235 buf.push(BYTE_ORDER_LE);
236 write_u32(buf, wkb_type);
237}
238
239fn write_u32(buf: &mut Vec<u8>, val: u32) {
240 buf.extend_from_slice(&val.to_le_bytes());
241}
242
243fn write_f64(buf: &mut Vec<u8>, val: f64) {
244 buf.extend_from_slice(&val.to_le_bytes());
245}
246
247fn read_geometry(data: &[u8], cursor: &mut usize, depth_remaining: usize) -> Option<Geometry> {
250 if depth_remaining == 0 {
251 return None;
252 }
253 let byte_order = read_u8(data, cursor)?;
254 let is_le = byte_order == 1;
255 let wkb_type = read_u32(data, cursor, is_le)?;
256
257 match wkb_type {
258 WKB_POINT => {
259 let x = read_f64(data, cursor, is_le)?;
260 let y = read_f64(data, cursor, is_le)?;
261 Some(Geometry::Point {
262 coordinates: [x, y],
263 })
264 }
265 WKB_LINESTRING => {
266 let count = read_u32(data, cursor, is_le)? as usize;
267 let coords = read_coords(data, cursor, count, is_le)?;
268 Some(Geometry::LineString {
269 coordinates: coords,
270 })
271 }
272 WKB_POLYGON => {
273 let count = read_u32(data, cursor, is_le)? as usize;
274 let capacity = checked_wkb_capacity(data, cursor, count, 4)?;
275 let mut rings = Vec::with_capacity(capacity);
276 for _ in 0..capacity {
277 let ring_count = read_u32(data, cursor, is_le)? as usize;
278 let ring = read_coords(data, cursor, ring_count, is_le)?;
279 rings.push(ring);
280 }
281 Some(Geometry::Polygon { coordinates: rings })
282 }
283 WKB_MULTIPOINT => {
284 let count = read_u32(data, cursor, is_le)? as usize;
285 let capacity = checked_wkb_capacity(data, cursor, count, 21)?;
286 let mut coords = Vec::with_capacity(capacity);
287 for _ in 0..capacity {
288 let inner = read_geometry(data, cursor, depth_remaining - 1)?;
289 if let Geometry::Point { coordinates } = inner {
290 coords.push(coordinates);
291 } else {
292 return None;
293 }
294 }
295 Some(Geometry::MultiPoint {
296 coordinates: coords,
297 })
298 }
299 WKB_MULTILINESTRING => {
300 let count = read_u32(data, cursor, is_le)? as usize;
301 let capacity = checked_wkb_capacity(data, cursor, count, 5)?;
302 let mut lines = Vec::with_capacity(capacity);
303 for _ in 0..capacity {
304 let inner = read_geometry(data, cursor, depth_remaining - 1)?;
305 if let Geometry::LineString { coordinates } = inner {
306 lines.push(coordinates);
307 } else {
308 return None;
309 }
310 }
311 Some(Geometry::MultiLineString { coordinates: lines })
312 }
313 WKB_MULTIPOLYGON => {
314 let count = read_u32(data, cursor, is_le)? as usize;
315 let capacity = checked_wkb_capacity(data, cursor, count, 5)?;
316 let mut polys = Vec::with_capacity(capacity);
317 for _ in 0..capacity {
318 let inner = read_geometry(data, cursor, depth_remaining - 1)?;
319 if let Geometry::Polygon { coordinates } = inner {
320 polys.push(coordinates);
321 } else {
322 return None;
323 }
324 }
325 Some(Geometry::MultiPolygon { coordinates: polys })
326 }
327 WKB_GEOMETRYCOLLECTION => {
328 let count = read_u32(data, cursor, is_le)? as usize;
329 let capacity = checked_wkb_capacity(data, cursor, count, 5)?;
330 let mut geoms = Vec::with_capacity(capacity);
331 for _ in 0..capacity {
332 geoms.push(read_geometry(data, cursor, depth_remaining - 1)?);
333 }
334 Some(Geometry::GeometryCollection { geometries: geoms })
335 }
336 _ => None,
337 }
338}
339
340fn checked_wkb_capacity(
346 data: &[u8],
347 cursor: &usize,
348 count: usize,
349 min_encoded_bytes: usize,
350) -> Option<usize> {
351 if count > MAX_WKB_ELEMENTS {
352 return None;
353 }
354 let required = count.checked_mul(min_encoded_bytes)?;
355 let remaining = data.len().checked_sub(*cursor)?;
356 (required <= remaining).then_some(count)
357}
358
359fn read_u8(data: &[u8], cursor: &mut usize) -> Option<u8> {
360 if *cursor >= data.len() {
361 return None;
362 }
363 let val = data[*cursor];
364 *cursor += 1;
365 Some(val)
366}
367
368fn read_u32(data: &[u8], cursor: &mut usize, is_le: bool) -> Option<u32> {
369 if *cursor + 4 > data.len() {
370 return None;
371 }
372 let bytes: [u8; 4] = [
373 data[*cursor],
374 data[*cursor + 1],
375 data[*cursor + 2],
376 data[*cursor + 3],
377 ];
378 *cursor += 4;
379 Some(if is_le {
380 u32::from_le_bytes(bytes)
381 } else {
382 u32::from_be_bytes(bytes)
383 })
384}
385
386fn read_f64(data: &[u8], cursor: &mut usize, is_le: bool) -> Option<f64> {
387 if *cursor + 8 > data.len() {
388 return None;
389 }
390 let bytes: [u8; 8] = [
391 data[*cursor],
392 data[*cursor + 1],
393 data[*cursor + 2],
394 data[*cursor + 3],
395 data[*cursor + 4],
396 data[*cursor + 5],
397 data[*cursor + 6],
398 data[*cursor + 7],
399 ];
400 *cursor += 8;
401 Some(if is_le {
402 f64::from_le_bytes(bytes)
403 } else {
404 f64::from_be_bytes(bytes)
405 })
406}
407
408fn read_coords(
409 data: &[u8],
410 cursor: &mut usize,
411 count: usize,
412 is_le: bool,
413) -> Option<Vec<[f64; 2]>> {
414 let capacity = checked_wkb_capacity(data, cursor, count, 16)?;
415 let mut coords = Vec::with_capacity(capacity);
416 for _ in 0..capacity {
417 let x = read_f64(data, cursor, is_le)?;
418 let y = read_f64(data, cursor, is_le)?;
419 coords.push([x, y]);
420 }
421 Some(coords)
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 fn geometry_to_wkb(geometry: &Geometry) -> Vec<u8> {
429 super::geometry_to_wkb(geometry).expect("test geometry must fit WKB limits")
430 }
431
432 #[test]
433 fn point_roundtrip() {
434 let geom = Geometry::point(-73.9857, 40.7484);
435 let wkb = geometry_to_wkb(&geom);
436 let decoded = geometry_from_wkb(&wkb).unwrap();
437 assert_eq!(geom, decoded);
438 }
439
440 #[test]
441 fn linestring_roundtrip() {
442 let geom = Geometry::line_string(vec![[0.0, 0.0], [1.0, 1.0], [2.0, 0.0]]);
443 let wkb = geometry_to_wkb(&geom);
444 let decoded = geometry_from_wkb(&wkb).unwrap();
445 assert_eq!(geom, decoded);
446 }
447
448 #[test]
449 fn polygon_roundtrip() {
450 let geom = Geometry::polygon(vec![
451 vec![
452 [0.0, 0.0],
453 [10.0, 0.0],
454 [10.0, 10.0],
455 [0.0, 10.0],
456 [0.0, 0.0],
457 ],
458 vec![[2.0, 2.0], [3.0, 2.0], [3.0, 3.0], [2.0, 3.0], [2.0, 2.0]], ]);
460 let wkb = geometry_to_wkb(&geom);
461 let decoded = geometry_from_wkb(&wkb).unwrap();
462 assert_eq!(geom, decoded);
463 }
464
465 #[test]
466 fn multipoint_roundtrip() {
467 let geom = Geometry::MultiPoint {
468 coordinates: vec![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]],
469 };
470 let wkb = geometry_to_wkb(&geom);
471 let decoded = geometry_from_wkb(&wkb).unwrap();
472 assert_eq!(geom, decoded);
473 }
474
475 #[test]
476 fn multilinestring_roundtrip() {
477 let geom = Geometry::MultiLineString {
478 coordinates: vec![
479 vec![[0.0, 0.0], [1.0, 1.0]],
480 vec![[2.0, 2.0], [3.0, 3.0], [4.0, 2.0]],
481 ],
482 };
483 let wkb = geometry_to_wkb(&geom);
484 let decoded = geometry_from_wkb(&wkb).unwrap();
485 assert_eq!(geom, decoded);
486 }
487
488 #[test]
489 fn multipolygon_roundtrip() {
490 let geom = Geometry::MultiPolygon {
491 coordinates: vec![
492 vec![vec![
493 [0.0, 0.0],
494 [1.0, 0.0],
495 [1.0, 1.0],
496 [0.0, 1.0],
497 [0.0, 0.0],
498 ]],
499 vec![vec![
500 [5.0, 5.0],
501 [6.0, 5.0],
502 [6.0, 6.0],
503 [5.0, 6.0],
504 [5.0, 5.0],
505 ]],
506 ],
507 };
508 let wkb = geometry_to_wkb(&geom);
509 let decoded = geometry_from_wkb(&wkb).unwrap();
510 assert_eq!(geom, decoded);
511 }
512
513 #[test]
514 fn geometry_collection_roundtrip() {
515 let geom = Geometry::GeometryCollection {
516 geometries: vec![
517 Geometry::point(1.0, 2.0),
518 Geometry::line_string(vec![[0.0, 0.0], [1.0, 1.0]]),
519 ],
520 };
521 let wkb = geometry_to_wkb(&geom);
522 let decoded = geometry_from_wkb(&wkb).unwrap();
523 assert_eq!(geom, decoded);
524 }
525
526 #[test]
527 fn parsed_max_counts_are_rejected_before_allocation() {
528 for geometry_type in [
529 WKB_LINESTRING,
530 WKB_POLYGON,
531 WKB_MULTIPOINT,
532 WKB_MULTILINESTRING,
533 WKB_MULTIPOLYGON,
534 WKB_GEOMETRYCOLLECTION,
535 ] {
536 let mut wkb = Vec::new();
537 write_header(&mut wkb, geometry_type);
538 write_u32(&mut wkb, u32::MAX);
539 assert!(geometry_from_wkb(&wkb).is_none(), "type={geometry_type}");
540 }
541 }
542
543 #[test]
544 fn encoder_rejects_total_output_above_byte_limit_before_writing() {
545 let geometry = Geometry::MultiLineString {
546 coordinates: (0..5).map(|_| vec![[0.0, 0.0]; 840_000]).collect(),
547 };
548 assert!(checked_encoded_size(&geometry, MAX_WKB_DEPTH).is_none());
549 assert!(super::geometry_to_wkb(&geometry).is_none());
550 }
551
552 #[test]
553 fn encoder_rejects_element_count_above_decoder_limit() {
554 let geometry = Geometry::LineString {
555 coordinates: vec![[0.0, 0.0]; MAX_WKB_ELEMENTS + 1],
556 };
557 assert!(super::geometry_to_wkb(&geometry).is_none());
558 }
559
560 #[test]
561 fn truncated_count_bodies_are_rejected_before_allocation() {
562 for geometry_type in [
563 WKB_LINESTRING,
564 WKB_POLYGON,
565 WKB_MULTIPOINT,
566 WKB_MULTILINESTRING,
567 WKB_MULTIPOLYGON,
568 WKB_GEOMETRYCOLLECTION,
569 ] {
570 let mut wkb = Vec::new();
571 write_header(&mut wkb, geometry_type);
572 write_u32(&mut wkb, 1);
573 assert!(geometry_from_wkb(&wkb).is_none(), "type={geometry_type}");
574 }
575 }
576
577 #[test]
578 fn geometry_collection_depth_is_bounded() {
579 let mut valid = Geometry::point(1.0, 2.0);
580 for _ in 0..MAX_WKB_DEPTH - 1 {
581 valid = Geometry::GeometryCollection {
582 geometries: vec![valid],
583 };
584 }
585 let valid_wkb = geometry_to_wkb(&valid);
586 assert_eq!(geometry_from_wkb(&valid_wkb), Some(valid));
587
588 let mut too_deep = Geometry::point(1.0, 2.0);
589 for _ in 0..MAX_WKB_DEPTH {
590 too_deep = Geometry::GeometryCollection {
591 geometries: vec![too_deep],
592 };
593 }
594 assert!(super::geometry_to_wkb(&too_deep).is_none());
595 }
596
597 #[test]
598 fn truncated_data_returns_none() {
599 let wkb = geometry_to_wkb(&Geometry::point(1.0, 2.0));
600 assert!(geometry_from_wkb(&wkb[..3]).is_none());
601 assert!(geometry_from_wkb(&[]).is_none());
602 }
603
604 #[test]
605 fn invalid_type_returns_none() {
606 let mut wkb = geometry_to_wkb(&Geometry::point(1.0, 2.0));
607 wkb[1] = 99; assert!(geometry_from_wkb(&wkb).is_none());
609 }
610
611 #[test]
612 fn wkb_bbox_extraction() {
613 let geom = Geometry::polygon(vec![vec![
614 [-10.0, -5.0],
615 [10.0, -5.0],
616 [10.0, 5.0],
617 [-10.0, 5.0],
618 [-10.0, -5.0],
619 ]]);
620 let wkb = geometry_to_wkb(&geom);
621 let bb = wkb_bbox(&wkb).unwrap();
622 assert_eq!(bb.min_lng, -10.0);
623 assert_eq!(bb.max_lng, 10.0);
624 assert_eq!(bb.min_lat, -5.0);
625 assert_eq!(bb.max_lat, 5.0);
626 }
627
628 #[test]
629 fn point_wkb_size() {
630 let wkb = geometry_to_wkb(&Geometry::point(0.0, 0.0));
631 assert_eq!(wkb.len(), 21);
633 }
634}