1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! Demo. Imports functions directly from the server and the client bypassing all
//! the HTTP stuff. Runs a demo/benchmark described step-by-step with the
//! Alice/Bob/Mallory roles.

use anyhow::Result;
use geohash::Coord;
use kdam::tqdm;
use rand::distributions::{Alphanumeric, DistString};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use std::time::Instant;

extern crate libreloc_server;
use libreloc_server::datastore;
use libreloc_server::datastore::*;

use libreloc_shared::*;

//

/// Number of blips to generate around the planet
const GLOBAL_DP_CNT: u32 = 1_000_000;

/// Number of blips uploaded by Bob
const BOB_N: usize = 18;

/// Number of blips uploaded by Bob and detected by Alice
const BOB_ALI_N: usize = 10;

/// Number of blips detected only by Alice. They are previously unknown to the
/// system so this is essentially noise.
const ALI_N: usize = 10;

/// Logs elapsed time
fn t(t0: Instant) -> Instant {
    println!("Done in [{:6}ms]", t0.elapsed().as_millis());
    Instant::now()
}

/// Calculate distance in meters between two locations using Haversine's formula
fn haversine_distance2(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
    let lat1_rad = lat1.to_radians();
    let lat2_rad = lat2.to_radians();

    // Haversine formula
    let dlat = lat2_rad - lat1_rad;
    let dlon = lon2.to_radians() - lon1.to_radians();
    let a =
        (dlat / 2.0).sin().powi(2) + lat1_rad.cos() * lat2_rad.cos() * (dlon / 2.0).sin().powi(2);
    let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
    let earth_radius_m = 6371000.0;

    earth_radius_m * c
}

fn store(ds: &mut datastore::MemDatastore, lat: f64, lon: f64, mac: &Mac, ssid: &str) {
    ds.insert_datapoint(lat, lon, mac, ssid);
}

/// Generates random blips around the world with a fixed PRNG seed. The distribution
/// is not meant to be realistic in any way.
fn generate_random_worldwide_blips(
    ds: &mut MemDatastore,
    rng: &mut StdRng,
    t0: &mut Instant,
    amount: u32,
) {
    for _ in tqdm!(0..amount) {
        let lat = rng.gen_range(-90.0..=90.0);
        let lon = rng.gen_range(-180.0..=180.0);
        let mac: Mac = rng.gen();
        let ssid = Alphanumeric.sample_string(rng, 16);

        // IRL the data would be uploaded to the server
        // The server stores the datapoint
        store(ds, lat, lon, &mac, &ssid);
    }
    println!(
        "{} writes per second",
        GLOBAL_DP_CNT as f32 / t0.elapsed().as_secs_f32()
    );
}

/// Generates random blips around Bob's position simulating datapoints from
/// somebody's home and surroundings
fn generate_random_localized_blips(
    ds: &mut MemDatastore,
    rng: &mut StdRng,
    center: &Coord,
) -> Blips {
    let mut bob_blips: Blips = vec![];
    for _ in 0..BOB_N {
        let r = 0.0003;
        let pos = Coord {
            x: center.x + rng.gen_range(-r..r),
            y: center.y + rng.gen_range(-r..r),
        };
        let mac: Mac = rng.gen();
        let ssid = Alphanumeric.sample_string(rng, 16);
        store(ds, pos.y, pos.x, &mac, &ssid);
        bob_blips.push((pos, mac, ssid));
    }
    bob_blips
}

#[tokio::main]
async fn main() -> Result<()> {
    let mut t0 = Instant::now();
    // let test_dir = Path::new("/dev/shm/libreloc_test");
    let mut rng: StdRng = SeedableRng::seed_from_u64(3);

    let recreate = true;
    // if recreate && test_dir.exists() {
    //     fs::remove_dir_all(test_dir)?;
    // }
    println!("Open datastore");
    // let ds = datastore::open_sled("/dev/shm/demodata")?;
    let mut ds = datastore::open_mem_datastore();
    if recreate {
        t0 = t(t0);
        // println!("Clean datastore");
        // ds.delete_all_data();
        // t0 = t(t0);

        // Populate dataset
        generate_random_worldwide_blips(&mut ds, &mut rng, &mut t0, 1000);
        t0 = t(t0);
    };
    // {
    //     let (k, v) = ds.wifi_bt.first_key_value().unwrap();
    //     assert_eq!("10000v-e13f", k);
    //     assert_eq!("0000v8x4f".to_owned(), v.location);
    // }

    println!("Bob uploads data from devices around home: https://geohash.jorren.nl/#sr2y7gt");
    let bob_pos = Coord {
        x: 12.477082,
        y: 41.899386,
    };
    let bob_blips = generate_random_localized_blips(&mut ds, &mut rng, &bob_pos);
    t0 = t(t0);

    println!("Alices visit Bob and runs geolocation");
    let alice_blips: Blips = {
        // Alice finds some of the APs in common with Bob and some are different
        let mut alice_blips: Blips = vec![];
        for b in bob_blips {
            // Copy some APs from Bob
            alice_blips.push(b);
            if alice_blips.len() >= BOB_ALI_N {
                break;
            }
        }
        // Add some APs that have never been seen before
        for _ in 0..ALI_N {
            let r = 0.0002;
            let pos = Coord {
                x: bob_pos.x + rng.gen_range(-r..r),
                y: bob_pos.y + rng.gen_range(-r..r),
            };
            let mac: Mac = rng.gen();
            let ssid = Alphanumeric.sample_string(&mut rng, 16);
            alice_blips.push((pos, mac, ssid));
        }
        alice_blips
    };

    let ali_real_pos = Coord {
        x: bob_pos.x + 0.0001,
        y: bob_pos.y + 0.0001,
    };

    println!("Populating dataset with random data using a fixed seed");

    let mut tot_blips_cnt = 1000;
    let mut increase = 512;
    while tot_blips_cnt < GLOBAL_DP_CNT {
        // println!("\nPhase 0: first step in zooming in. Alice's phone has no approx location yet.");
        let mut geopath = "".to_string();
        for step in 0..4 {
            // println!("\n -- Phase {step}: zooming in. --");
            let (sel, segment) = lookup_step(&ds.wifi_bt_bh, &alice_blips, &geopath);
            geopath.push_str(&segment);
            let dist = haversine_distance_gh(&ali_real_pos, &geopath);
            println!(
                "Alice zooms in to https://geohash.jorren.nl/#{:<10} Sel {:<2} Distance: {:<8}",
                geopath,
                (sel * 100.0) as u8,
                dist
            );
        }

        generate_random_worldwide_blips(&mut ds, &mut rng, &mut t0, increase);
        tot_blips_cnt += increase;
        increase *= 2;
    }

    // assert_eq!("sr2y7gtd", geopath);
    println!("SUCCESS");
    t(t0);

    Ok(())
}