1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// For OSX 10.5 we use the system API function WirelessScanSplit. This function
6// is not documented or included in the SDK, so we use a reverse-engineered
7// header, osx_wifi_.h. This file is taken from the iStumbler project
8// (http://www.istumbler.net).
9
10#include "content/browser/geolocation/wifi_data_provider_mac.h"
11
12#include <dlfcn.h>
13#include <stdio.h>
14
15#include "base/strings/utf_string_conversions.h"
16#include "content/browser/geolocation/osx_wifi.h"
17#include "content/browser/geolocation/wifi_data_provider_common.h"
18
19namespace content {
20namespace {
21// The time periods, in milliseconds, between successive polls of the wifi data.
22const int kDefaultPollingInterval = 120000;  // 2 mins
23const int kNoChangePollingInterval = 300000;  // 5 mins
24const int kTwoNoChangePollingInterval = 600000;  // 10 mins
25const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
26
27// Provides the wifi API binding for use when running on OSX 10.5 machines using
28// the Apple80211 framework.
29class Apple80211Api : public WifiDataProviderCommon::WlanApiInterface {
30 public:
31  Apple80211Api();
32  virtual ~Apple80211Api();
33
34  // Must be called before any other interface method. Will return false if the
35  // Apple80211 framework cannot be initialized (e.g. running on post-10.5 OSX),
36  // in which case no other method may be called.
37  bool Init();
38
39  // WlanApiInterface
40  virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data) OVERRIDE;
41
42 private:
43  // Handle, context and function pointers for Apple80211 library.
44  void* apple_80211_library_;
45  WirelessContext* wifi_context_;
46  WirelessAttachFunction WirelessAttach_function_;
47  WirelessScanSplitFunction WirelessScanSplit_function_;
48  WirelessDetachFunction WirelessDetach_function_;
49
50  WifiData wifi_data_;
51
52  DISALLOW_COPY_AND_ASSIGN(Apple80211Api);
53};
54
55Apple80211Api::Apple80211Api()
56    : apple_80211_library_(NULL), wifi_context_(NULL),
57      WirelessAttach_function_(NULL), WirelessScanSplit_function_(NULL),
58      WirelessDetach_function_(NULL) {
59}
60
61Apple80211Api::~Apple80211Api() {
62  if (WirelessDetach_function_)
63    (*WirelessDetach_function_)(wifi_context_);
64  dlclose(apple_80211_library_);
65}
66
67bool Apple80211Api::Init() {
68  DVLOG(1) << "Apple80211Api::Init";
69  apple_80211_library_ = dlopen(
70      "/System/Library/PrivateFrameworks/Apple80211.framework/Apple80211",
71      RTLD_LAZY);
72  if (!apple_80211_library_) {
73    DLOG(WARNING) << "Could not open Apple80211 library";
74    return false;
75  }
76  WirelessAttach_function_ = reinterpret_cast<WirelessAttachFunction>(
77      dlsym(apple_80211_library_, "WirelessAttach"));
78  WirelessScanSplit_function_ = reinterpret_cast<WirelessScanSplitFunction>(
79      dlsym(apple_80211_library_, "WirelessScanSplit"));
80  WirelessDetach_function_ = reinterpret_cast<WirelessDetachFunction>(
81      dlsym(apple_80211_library_, "WirelessDetach"));
82  DCHECK(WirelessAttach_function_);
83  DCHECK(WirelessScanSplit_function_);
84  DCHECK(WirelessDetach_function_);
85
86  if (!WirelessAttach_function_ || !WirelessScanSplit_function_ ||
87      !WirelessDetach_function_) {
88    DLOG(WARNING) << "Symbol error. Attach: " << !!WirelessAttach_function_
89        << " Split: " << !!WirelessScanSplit_function_
90        << " Detach: " << !!WirelessDetach_function_;
91    return false;
92  }
93
94  WIErr err = (*WirelessAttach_function_)(&wifi_context_, 0);
95  if (err != noErr) {
96    DLOG(WARNING) << "Error attaching: " << err;
97    return false;
98  }
99  return true;
100}
101
102bool Apple80211Api::GetAccessPointData(WifiData::AccessPointDataSet* data) {
103  DVLOG(1) << "Apple80211Api::GetAccessPointData";
104  DCHECK(data);
105  DCHECK(WirelessScanSplit_function_);
106  CFArrayRef managed_access_points = NULL;
107  CFArrayRef adhoc_access_points = NULL;
108  // Arrays returned here are owned by the caller.
109  WIErr err = (*WirelessScanSplit_function_)(wifi_context_,
110                                             &managed_access_points,
111                                             &adhoc_access_points,
112                                             0);
113  if (err != noErr) {
114    DLOG(WARNING) << "Error spliting scan: " << err;
115    return false;
116  }
117
118  if (managed_access_points == NULL) {
119    DLOG(WARNING) << "managed_access_points == NULL";
120    return false;
121  }
122
123  int num_access_points = CFArrayGetCount(managed_access_points);
124  DVLOG(1) << "Found " << num_access_points << " managed access points";
125  for (int i = 0; i < num_access_points; ++i) {
126    const WirelessNetworkInfo* access_point_info =
127        reinterpret_cast<const WirelessNetworkInfo*>(
128        CFDataGetBytePtr(
129        reinterpret_cast<const CFDataRef>(
130        CFArrayGetValueAtIndex(managed_access_points, i))));
131
132    // Currently we get only MAC address, signal strength, channel
133    // signal-to-noise and SSID
134    AccessPointData access_point_data;
135    access_point_data.mac_address =
136        MacAddressAsString16(access_point_info->macAddress);
137    // WirelessNetworkInfo::signal appears to be signal strength in dBm.
138    access_point_data.radio_signal_strength = access_point_info->signal;
139    access_point_data.channel = access_point_info->channel;
140    // WirelessNetworkInfo::noise appears to be noise floor in dBm.
141    access_point_data.signal_to_noise = access_point_info->signal -
142                                        access_point_info->noise;
143    if (!UTF8ToUTF16(reinterpret_cast<const char*>(access_point_info->name),
144                     access_point_info->nameLen,
145                     &access_point_data.ssid)) {
146      access_point_data.ssid.clear();
147    }
148    data->insert(access_point_data);
149  }
150
151  if (managed_access_points)
152    CFRelease(managed_access_points);
153  if (adhoc_access_points)
154    CFRelease(adhoc_access_points);
155
156  return true;
157}
158}  // namespace
159
160// static
161WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
162  return new MacWifiDataProvider();
163}
164
165MacWifiDataProvider::MacWifiDataProvider() {
166}
167
168MacWifiDataProvider::~MacWifiDataProvider() {
169}
170
171MacWifiDataProvider::WlanApiInterface* MacWifiDataProvider::NewWlanApi() {
172  // Try and find a API binding that works: first try the officially supported
173  // CoreWLAN API, and if this fails (e.g. on OSX 10.5) fall back to the reverse
174  // engineered Apple80211 API.
175  MacWifiDataProvider::WlanApiInterface* core_wlan_api = NewCoreWlanApi();
176  if (core_wlan_api)
177    return core_wlan_api;
178
179  scoped_ptr<Apple80211Api> wlan_api(new Apple80211Api);
180  if (wlan_api->Init())
181    return wlan_api.release();
182
183  DVLOG(1) << "MacWifiDataProvider : failed to initialize any wlan api";
184  return NULL;
185}
186
187WifiPollingPolicy* MacWifiDataProvider::NewPollingPolicy() {
188  return new GenericWifiPollingPolicy<kDefaultPollingInterval,
189                                      kNoChangePollingInterval,
190                                      kTwoNoChangePollingInterval,
191                                      kNoWifiPollingIntervalMilliseconds>;
192}
193
194}  // namespace content
195