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 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 53 BGeolocation::BGeolocation() 54 : fGeolocationService(kDefaultGeolocationService), 55 fGeocodingService(kDefaultGeocodingService) 56 { 57 } 58 59 60 BGeolocation::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 72 status_t 73 BGeolocation::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 178 status_t 179 BGeolocation::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 234 const char* BGeolocation::kDefaultGeolocationService 235 = "https://location.services.mozilla.com/v1/geolocate?key=" 236 DEFAULT_GEOLOCATION_SERVICE_KEY; 237 238 const char* BGeolocation::kDefaultGeocodingService 239 = "https://secure.geonames.org/?username=" 240 DEFAULT_GEOCODING_SERVICE_KEY; 241 242 #else 243 244 const char* BGeolocation::kDefaultGeolocationService = ""; 245 const char* BGeolocation::kDefaultGeocodingService = ""; 246 247 #endif 248 249 } // namespace Network 250 251 } // namespace BPrivate 252