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
use protobuf::Message;
use thiserror::Error;

use crate::constants::{API_BASE, COORD_ERROR, H_IDENTIFIER, H_LOCALE, H_VERSION, USER_AGENT};
use crate::Error::{BssidNotFound, QueryError};
use crate::gsloc_proto::request::RequestWifi;
use crate::gsloc_proto::Response;

mod constants;
mod gsloc_proto;

macro_rules! string {
    ($ss:expr) => {String::from_utf8($ss).unwrap()};
}

#[derive(Error, Debug)]
pub enum Error {
    #[error("The BSSID \"{0}\" was not found.")]
    BssidNotFound(String),

    #[error("Query error: {0}")]
    QueryError(String),
}

#[inline(always)]
fn be_i16(num: i16) -> Vec<u8> {
    num.to_be_bytes().into()
}

#[inline(always)]
fn coord(coord: i64) -> f64 {
    coord as f64 * 1e-8
}

fn payload_header() -> Vec<u8> {
    const NUL_SQH: &str = "\x00\x01";
    const NUL_NUL: &str = "\x00\x00";

    let locale_length = be_i16(H_LOCALE.len() as i16);
    let identifier_length = be_i16(H_IDENTIFIER.len() as i16);
    let version_length = be_i16(H_VERSION.len() as i16);

    let result = format!(
        "{}{}{}{}{}{}{}{}{}{}",
        NUL_SQH,
        string!(locale_length),
        H_LOCALE,
        string!(identifier_length),
        H_IDENTIFIER,
        string!(version_length),
        H_VERSION,
        NUL_NUL,
        NUL_SQH,
        NUL_NUL
    );

    result.into_bytes()
}

fn create_payload(bssids: &[&str], signal: i32, noise: i32) -> Vec<u8> {
    let wifis: Vec<RequestWifi> = bssids
        .to_vec()
        .iter()
        .map(|s| RequestWifi {
            mac: Some(s.to_string()),
            special_fields: Default::default(),
        })
        .collect();

    let request = gsloc_proto::Request {
        wifis,
        noise: Some(noise),
        signal: Some(signal),
        source: None,
        special_fields: Default::default(),
    };

    let mut serialized = request.write_to_bytes().unwrap();

    serialized.splice(..0, be_i16(serialized.len() as i16));
    serialized.splice(..0, payload_header());


    serialized
}

fn send(payload: &[u8]) -> Result<Response, Error> {
    let client = reqwest::blocking::Client::new();

    let http_res = client
        .post(API_BASE)
        .header("User-Agent", USER_AGENT)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .body(payload.to_vec())
        .send()
        .map_err(|e| QueryError(e.to_string()))?;

    if http_res.status().is_server_error() || http_res.status().is_client_error() {
        panic!("HTTP ERROR: {}", http_res.status().as_u16())
    }

    let resp_bytes = http_res.bytes().unwrap();

    let mut response = Response::new();
    response
        .merge_from_bytes(&resp_bytes.to_vec().as_slice()[10..])
        .expect("Failed to parse response.");

    Ok(response)
}

pub fn basic_location(bssid: &str) -> Result<(f64, f64), Error> {
    let payload = create_payload(&[bssid], 100, 0);

    let response = send(&payload)?;

    if response.wifis.len() == 0 {
        return Err(BssidNotFound(bssid.to_string()));
    }

    let wifi_location = response.wifis[0].location.clone();

    if wifi_location.latitude.unwrap() as u64 == COORD_ERROR {
        return Err(BssidNotFound(bssid.to_string()));
    }

    let lat = coord(wifi_location.latitude.unwrap());
    let long = coord(wifi_location.longitude.unwrap());

    Ok((lat, long))
}