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