Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_net/tests/range_source.rs

5.0 KiB, 1 run

created by r1870400018:60844, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! A PMTiles archive read over HTTP by range, from a local server holding only the byte ranges
2//! the reference `pmtiles` reader read from the Protomaps planet (see
3//! `fe2o3_geom/tests/data/protomaps/NOTICE.md`).
4
5use oxedyne_fe2o3_geom::tile::pmtiles::{
6 Archive,
7 RangeSource,
8};
9use oxedyne_fe2o3_net::http::range_source::HttpRangeSource;
10
11use oxedyne_fe2o3_core::prelude::*;
12
13use std::{
14 io::{
15 Read,
16 Write,
17 },
18 net::TcpListener,
19 path::PathBuf,
20 thread,
21};
22
23fn ranges() -> Outcome<Vec<(u64, Vec<u8>)>> {
24 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
25 .join("../fe2o3_geom/tests/data/protomaps/ocean_ranges.bin");
26 let b = res!(std::fs::read(&path), File, Read);
27 let mut out = Vec::new();
28 let mut i = 0usize;
29 while i + 12 <= b.len() {
30 let mut o = [0u8; 8];
31 o.copy_from_slice(&b[i..i + 8]);
32 let n = u32::from_le_bytes([b[i + 8], b[i + 9], b[i + 10], b[i + 11]]) as usize;
33 out.push((u64::from_le_bytes(o), b[i + 12..i + 12 + n].to_vec()));
34 i += 12 + n;
35 }
36 Ok(out)
37}
38
39/// Serves `n` requests: a `Range` within the known ranges is answered `206` on `/planet`, and
40/// `/whole` answers `200` with a body, as a server ignoring `Range` does.
41fn serve(n: usize) -> Outcome<u16> {
42 let data = res!(ranges());
43 let listener = res!(TcpListener::bind("127.0.0.1:0"), Network, Init);
44 let port = res!(listener.local_addr(), Network, Init).port();
45 thread::spawn(move || {
46 for _ in 0..n {
47 let (mut s, _) = match listener.accept() {
48 Ok(c) => c,
49 Err(_) => return,
50 };
51 let mut req = Vec::new();
52 let mut buf = [0u8; 1024];
53 while !req.windows(4).any(|w| w == b"\r\n\r\n") {
54 match s.read(&mut buf) {
55 Ok(0) | Err(_) => break,
56 Ok(k) => req.extend_from_slice(&buf[..k]),
57 }
58 }
59 let text = String::from_utf8_lossy(&req).to_string();
60 let whole = text.starts_with("GET /whole ");
61 let range = text.lines()
62 .find_map(|l| l.strip_prefix("Range: bytes="))
63 .and_then(|r| r.split_once('-'))
64 .and_then(|(a, b)| Some((a.trim().parse::<u64>().ok()?, b.trim().parse::<u64>().ok()?)));
65 let reply = match (whole, range) {
66 (true, _) => {
67 let body = vec![7u8; 64];
68 let mut r = fmt!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n", body.len()).into_bytes();
69 r.extend_from_slice(&body);
70 r
71 },
72 (false, Some((a, b))) => {
73 let hit = data.iter().find(|(o, d)| a >= *o && b < *o + d.len() as u64);
74 match hit {
75 Some((o, d)) => {
76 let body = &d[(a - o) as usize..=(b - o) as usize];
77 let mut r = fmt!("HTTP/1.1 206 Partial Content\r\nContent-Length: {}\r\n\
78 Content-Range: bytes {}-{}/*\r\n\r\n", body.len(), a, b).into_bytes();
79 r.extend_from_slice(body);
80 r
81 },
82 None => b"HTTP/1.1 416 Range Not Satisfiable\r\nContent-Length: 0\r\n\r\n".to_vec(),
83 }
84 },
85 (false, None) => b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n".to_vec(),
86 };
87 let _ = s.write_all(&reply);
88 }
89 });
90 Ok(port)
91}
92
93fn fnv1a64(b: &[u8]) -> String {
94 let mut h: u64 = 0xcbf29ce484222325;
95 for x in b {
96 h ^= *x as u64;
97 h = h.wrapping_mul(0x100000001b3);
98 }
99 fmt!("{:016x}", h)
100}
101
102#[test]
103fn test_an_archive_reads_over_http_ranges_00() -> Outcome<()> {
104 // Header, root, one leaf and one tile for the first; the second's leaf and the shared tile.
105 let port = res!(serve(8));
106 let src = res!(HttpRangeSource::new(&fmt!("http://127.0.0.1:{}/planet", port), None));
107 let a = res!(Archive::open(src));
108 req!(a.header().max_zoom, 15);
109 // The two open-ocean tiles, and the reference reader's reading of them.
110 for (z, x, y) in [(12u8, 2958u32, 2545u32), (15, 5152, 21497)] {
111 let t = res!(res!(a.tile_decoded(z, x, y)).ok_or_else(|| err!("{}/{}/{} is missing.", z, x, y; Test)));
112 req!(t.len(), 75);
113 req!(fnv1a64(&t), "b36d34914f4c291c".to_string(), "{}/{}/{} read other bytes.", z, x, y);
114 }
115 Ok(())
116}
117
118#[test]
119fn test_a_server_ignoring_range_is_refused_01() -> Outcome<()> {
120 let port = res!(serve(1));
121 let src = res!(HttpRangeSource::new(&fmt!("http://127.0.0.1:{}/whole", port), None));
122 let refused = src.read(0, 16).is_err();
123 req!(refused, true, "A 200 with the whole file was read as a range.");
124 req!(HttpRangeSource::new("ftp://example.com/x", None).is_err(), true, "An ftp URL was taken.");
125 req!(HttpRangeSource::new("https://example.com/x", None).is_err(), true,
126 "An https URL was taken with no TLS configuration.");
127 Ok(())
128}