1/*
2 * Copyright 2014, Haiku, Inc. All Rights Reserved.
3 * Copyright 2019, Adrien Destugues, pulkomandy@pulkomandy.tk
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include <Geolocation.h>
9
10#include <HttpRequest.h>
11#include <Json.h>
12#include <NetworkDevice.h>
13#include <NetworkInterface.h>
14#include <NetworkRoster.h>
15#include <String.h>
16#include <UrlProtocolRoster.h>
17#include <UrlRequest.h>
18
19
20namespace BPrivate {
21
22namespace Network {
23
24class GeolocationListener: public BUrlProtocolListener
25{
26	public:
27		GeolocationListener()
28		{
29			pthread_cond_init(&fCompletion, NULL);
30			pthread_mutex_init(&fLock, NULL);
31		}
32
33		virtual	~GeolocationListener() {
34			pthread_cond_destroy(&fCompletion);
35			pthread_mutex_destroy(&fLock);
36		}
37
38		void ConnectionOpened(BUrlRequest* caller)
39		{
40			pthread_mutex_lock(&fLock);
41		}
42
43		void RequestCompleted(BUrlRequest* caller, bool success) {
44			pthread_cond_signal(&fCompletion);
45			pthread_mutex_unlock(&fLock);
46		}
47
48		pthread_cond_t fCompletion;
49		pthread_mutex_t fLock;
50};
51
52
53BGeolocation::BGeolocation()
54	: fGeolocationService(kDefaultGeolocationService),
55	fGeocodingService(kDefaultGeocodingService)
56{
57}
58
59
60BGeolocation::BGeolocation(const BUrl& geolocationService,
61	const BUrl& geocodingService)
62	: fGeolocationService(geolocationService),
63	fGeocodingService(geocodingService)
64{
65	if (!fGeolocationService.IsValid())
66		fGeolocationService.SetUrlString(kDefaultGeolocationService);
67	if (!fGeocodingService.IsValid())
68		fGeocodingService.SetUrlString(kDefaultGeocodingService);
69}
70
71
72status_t
73BGeolocation::LocateSelf(float& latitude, float& longitude)
74{
75	// Enumerate wifi network and build JSON message
76	BNetworkRoster& roster = BNetworkRoster::Default();
77	uint32 interfaceCookie = 0;
78	BNetworkInterface interface;
79
80	BString query("{\n\t\"wifiAccessPoints\": [");
81	int32 count = 0;
82
83	while (roster.GetNextInterface(&interfaceCookie, interface) == B_OK) {
84		BNetworkDevice device(interface.Name());
85			// TODO is that the correct way to enumerate devices?
86
87		uint32 networksCount = 0;
88		wireless_network* networks = NULL;
89		device.GetNetworks(networks, networksCount);
90		for (uint32 i = 0; i < networksCount; i++) {
91			const wireless_network& network = networks[i];
92			if (count != 0)
93				query += ',';
94
95			count++;
96
97			query += "\n\t\t{ \"macAddress\": \"";
98			query += network.address.ToString().ToUpper();
99			query += "\", \"signalStrength\": ";
100			query << (int)network.signal_strength;
101			query += ", \"signalToNoiseRatio\": ";
102			query << (int)network.noise_level;
103			query += " }";
104		}
105		delete[] networks;
106	}
107
108	query += "\n\t]\n}\n";
109
110	// Check that we have enough data (we need at least 2 networks)
111	if (count < 2)
112		return B_DEVICE_NOT_FOUND;
113
114	GeolocationListener listener;
115	BMallocIO resultBuffer;
116
117	// Send Request (POST JSON message)
118	BUrlRequest* request = BUrlProtocolRoster::MakeRequest(fGeolocationService,
119		&resultBuffer, &listener);
120	if (request == NULL)
121		return B_BAD_DATA;
122
123	BHttpRequest* http = dynamic_cast<BHttpRequest*>(request);
124	if (http == NULL) {
125		delete request;
126		return B_BAD_DATA;
127	}
128
129	http->SetMethod(B_HTTP_POST);
130	BMemoryIO* io = new BMemoryIO(query.String(), query.Length());
131	http->AdoptInputData(io, query.Length());
132
133	status_t result = http->Run();
134	if (result < 0) {
135		delete http;
136		return result;
137	}
138
139	pthread_mutex_lock(&listener.fLock);
140	while (http->IsRunning())
141		pthread_cond_wait(&listener.fCompletion, &listener.fLock);
142	pthread_mutex_unlock(&listener.fLock);
143
144	// Parse reply
145	const BHttpResult& reply = (const BHttpResult&)http->Result();
146	if (reply.StatusCode() != 200) {
147		delete http;
148		return B_ERROR;
149	}
150
151	BMessage data;
152	result = BJson::Parse((char*)resultBuffer.Buffer(), data);
153	delete http;
154	if (result != B_OK) {
155		return result;
156	}
157
158	BMessage location;
159	result = data.FindMessage("location", &location);
160	if (result != B_OK)
161		return result;
162
163	double lat, lon;
164	result = location.FindDouble("lat", &lat);
165	if (result != B_OK)
166		return result;
167	result = location.FindDouble("lng", &lon);
168	if (result != B_OK)
169		return result;
170
171	latitude = lat;
172	longitude = lon;
173
174	return result;
175}
176
177
178status_t
179BGeolocation::Country(const float latitude, const float longitude,
180	BCountry& country)
181{
182	// Prepare the request URL
183	BUrl url(fGeocodingService);
184	BString requestString;
185	requestString.SetToFormat("%s&lat=%f&lng=%f", url.Request().String(), latitude,
186		longitude);
187	url.SetPath("/countryCode");
188	url.SetRequest(requestString);
189
190	GeolocationListener listener;
191	BMallocIO resultBuffer;
192	BUrlRequest* request = BUrlProtocolRoster::MakeRequest(url,
193		&resultBuffer, &listener);
194	if (request == NULL)
195		return B_BAD_DATA;
196
197	BHttpRequest* http = dynamic_cast<BHttpRequest*>(request);
198	if (http == NULL) {
199		delete request;
200		return B_BAD_DATA;
201	}
202
203	status_t result = http->Run();
204	if (result < 0) {
205		delete http;
206		return result;
207	}
208
209	pthread_mutex_lock(&listener.fLock);
210	while (http->IsRunning()) {
211		pthread_cond_wait(&listener.fCompletion, &listener.fLock);
212	}
213	pthread_mutex_unlock(&listener.fLock);
214
215	// Parse reply
216	const BHttpResult& reply = (const BHttpResult&)http->Result();
217	if (reply.StatusCode() != 200) {
218		delete http;
219		return B_ERROR;
220	}
221
222	off_t length = 0;
223	resultBuffer.GetSize(&length);
224	length -= 2; // Remove \r\n from response
225	BString countryCode((char*)resultBuffer.Buffer(), (int32)length);
226	return country.SetTo(countryCode);
227}
228
229
230#ifdef HAVE_DEFAULT_GEOLOCATION_SERVICE_KEY
231
232#include "DefaultGeolocationServiceKey.h"
233
234const char* BGeolocation::kDefaultGeolocationService
235	= "https://location.services.mozilla.com/v1/geolocate?key="
236		DEFAULT_GEOLOCATION_SERVICE_KEY;
237
238const char* BGeolocation::kDefaultGeocodingService
239	= "https://secure.geonames.org/?username="
240		DEFAULT_GEOCODING_SERVICE_KEY;
241
242#else
243
244const char* BGeolocation::kDefaultGeolocationService = "";
245const char* BGeolocation::kDefaultGeocodingService = "";
246
247#endif
248
249}	// namespace Network
250
251}	// namespace BPrivate
252