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