1// Copyright 2015 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#include "device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h"
6
7#include "base/mac/foundation_util.h"
8#include "base/mac/scoped_nsobject.h"
9#include "device/bluetooth/test/bluetooth_test_mac.h"
10#include "device/bluetooth/test/mock_bluetooth_cbcharacteristic_mac.h"
11#include "device/bluetooth/test/mock_bluetooth_cbdescriptor_mac.h"
12#include "device/bluetooth/test/mock_bluetooth_cbservice_mac.h"
13
14using base::mac::ObjCCast;
15using base::scoped_nsobject;
16
17@interface MockCBPeripheral () {
18  scoped_nsobject<NSUUID> _identifier;
19  scoped_nsobject<NSString> _name;
20  id<CBPeripheralDelegate> _delegate;
21  scoped_nsobject<NSMutableArray> _services;
22}
23
24@end
25
26@implementation MockCBPeripheral
27
28@synthesize state = _state;
29@synthesize delegate = _delegate;
30@synthesize bluetoothTestMac = _bluetoothTestMac;
31
32- (instancetype)init {
33  [self doesNotRecognizeSelector:_cmd];
34  return self;
35}
36
37- (instancetype)initWithUTF8StringIdentifier:(const char*)utf8Identifier {
38  return [self initWithUTF8StringIdentifier:utf8Identifier name:nil];
39}
40
41- (instancetype)initWithUTF8StringIdentifier:(const char*)utf8Identifier
42                                        name:(NSString*)name {
43  scoped_nsobject<NSUUID> identifier(
44      [[NSUUID alloc] initWithUUIDString:@(utf8Identifier)]);
45  return [self initWithIdentifier:identifier name:name];
46}
47
48- (instancetype)initWithIdentifier:(NSUUID*)identifier name:(NSString*)name {
49  self = [super init];
50  if (self) {
51    _identifier.reset([identifier retain]);
52    if (name) {
53      _name.reset([name retain]);
54    }
55    _state = CBPeripheralStateDisconnected;
56  }
57  return self;
58}
59
60- (BOOL)isKindOfClass:(Class)aClass {
61  if (aClass == [CBPeripheral class] ||
62      [aClass isSubclassOfClass:[CBPeripheral class]]) {
63    return YES;
64  }
65  return [super isKindOfClass:aClass];
66}
67
68- (BOOL)isMemberOfClass:(Class)aClass {
69  if (aClass == [CBPeripheral class] ||
70      [aClass isSubclassOfClass:[CBPeripheral class]]) {
71    return YES;
72  }
73  return [super isKindOfClass:aClass];
74}
75
76- (void)setState:(CBPeripheralState)state {
77  _state = state;
78  if (_state == CBPeripheralStateDisconnected) {
79    _services.reset();
80  }
81}
82
83- (void)discoverServices:(NSArray*)serviceUUIDs {
84  if (_bluetoothTestMac) {
85    _bluetoothTestMac->OnFakeBluetoothServiceDiscovery();
86  }
87}
88
89- (void)discoverCharacteristics:(NSArray*)characteristics
90                     forService:(CBService*)service {
91  if (_bluetoothTestMac) {
92    _bluetoothTestMac->OnFakeBluetoothCharacteristicDiscovery();
93  }
94}
95
96- (void)discoverDescriptorsForCharacteristic:(CBCharacteristic*)characteristic {
97}
98
99- (void)readValueForCharacteristic:(CBCharacteristic*)characteristic {
100  DCHECK(_bluetoothTestMac);
101  _bluetoothTestMac->OnFakeBluetoothCharacteristicReadValue();
102}
103
104- (void)writeValue:(NSData*)data
105    forCharacteristic:(CBCharacteristic*)characteristic
106                 type:(CBCharacteristicWriteType)type {
107  DCHECK(_bluetoothTestMac);
108  const uint8_t* buffer = static_cast<const uint8_t*>(data.bytes);
109  std::vector<uint8_t> value(buffer, buffer + data.length);
110  _bluetoothTestMac->OnFakeBluetoothCharacteristicWriteValue(value);
111}
112
113- (void)readValueForDescriptor:(CBDescriptor*)descriptor {
114  DCHECK(_bluetoothTestMac);
115  _bluetoothTestMac->OnFakeBluetoothDescriptorReadValue();
116}
117
118- (void)writeValue:(NSData*)data forDescriptor:(CBDescriptor*)descriptor {
119  DCHECK(_bluetoothTestMac);
120  const uint8_t* buffer = static_cast<const uint8_t*>(data.bytes);
121  std::vector<uint8_t> value(buffer, buffer + data.length);
122  _bluetoothTestMac->OnFakeBluetoothDescriptorWriteValue(value);
123}
124
125- (void)removeAllServices {
126  [_services removeAllObjects];
127}
128
129- (void)addServices:(NSArray*)services {
130  if (!_services) {
131    _services.reset([[NSMutableArray alloc] init]);
132  }
133  for (CBUUID* uuid in services) {
134    base::scoped_nsobject<MockCBService> service([[MockCBService alloc]
135        initWithPeripheral:self.peripheral
136                    CBUUID:uuid
137                   primary:YES]);
138    [_services addObject:[service service]];
139  }
140}
141
142- (void)mockDidDiscoverServicesWithError:(NSError*)error {
143  [_delegate peripheral:self.peripheral didDiscoverServices:error];
144}
145
146- (void)removeService:(CBService*)service {
147  base::scoped_nsobject<CBService> serviceToRemove(service,
148                                                   base::scoped_policy::RETAIN);
149  DCHECK(serviceToRemove);
150  [_services removeObject:serviceToRemove];
151  [self didModifyServices:@[ serviceToRemove ]];
152}
153
154- (void)mockDidDiscoverServices {
155  [_delegate peripheral:self.peripheral didDiscoverServices:nil];
156}
157
158- (void)mockDidDiscoverCharacteristicsForService:(CBService*)service {
159  [_delegate peripheral:self.peripheral
160      didDiscoverCharacteristicsForService:service
161                                     error:nil];
162}
163
164- (void)mockDidDiscoverCharacteristicsForService:(CBService*)service
165                                       WithError:(NSError*)error {
166  [_delegate peripheral:self.peripheral
167      didDiscoverCharacteristicsForService:service
168                                     error:error];
169}
170
171- (void)mockDidDiscoverDescriptorsForCharacteristic:
172    (CBCharacteristic*)characteristic {
173  [_delegate peripheral:self.peripheral
174      didDiscoverDescriptorsForCharacteristic:characteristic
175                                        error:nil];
176}
177
178- (void)mockDidDiscoverDescriptorsForCharacteristic:
179            (CBCharacteristic*)characteristic
180                                          WithError:(NSError*)error {
181  [_delegate peripheral:self.peripheral
182      didDiscoverDescriptorsForCharacteristic:characteristic
183                                        error:error];
184}
185
186- (void)mockDidDiscoverEvents {
187  [self mockDidDiscoverServices];
188  // BluetoothLowEnergyDeviceMac is expected to call
189  // -[CBPeripheral discoverCharacteristics:forService:] for each services,
190  // so -[<CBPeripheralDelegate peripheral:didDiscoverCharacteristicsForService:
191  // error:] needs to be called for all services.
192  for (CBService* service in _services.get()) {
193    [self mockDidDiscoverCharacteristicsForService:service];
194    for (CBCharacteristic* characteristic in service.characteristics) {
195      // After discovering services, BluetoothLowEnergyDeviceMac is expected to
196      // discover characteristics for all services.
197      [_delegate peripheral:self.peripheral
198          didDiscoverDescriptorsForCharacteristic:characteristic
199                                            error:nil];
200    }
201  }
202}
203
204- (void)didModifyServices:(NSArray*)invalidatedServices {
205  DCHECK(
206      [_delegate respondsToSelector:@selector(peripheral:didModifyServices:)]);
207  [_delegate peripheral:self.peripheral didModifyServices:invalidatedServices];
208}
209
210- (void)didDiscoverDescriptorsWithCharacteristic:
211    (MockCBCharacteristic*)characteristic_mock {
212  [_delegate peripheral:self.peripheral
213      didDiscoverDescriptorsForCharacteristic:characteristic_mock.characteristic
214                                        error:nil];
215}
216
217- (NSUUID*)identifier {
218  return _identifier;
219}
220
221- (NSString*)name {
222  return _name;
223}
224
225- (NSArray*)services {
226  return _services;
227}
228
229- (CBPeripheral*)peripheral {
230  return ObjCCast<CBPeripheral>(self);
231}
232
233- (void)setNotifyValue:(BOOL)notification
234     forCharacteristic:(CBCharacteristic*)characteristic {
235  _bluetoothTestMac->OnFakeBluetoothGattSetCharacteristicNotification(
236      notification == YES);
237}
238
239@end
240