using System;
using System.Collections.Generic;
using System.Threading;
namespace CoreBluetooth
{
public interface ICBPeripheralManagerDelegate
{
void DidUpdateState(CBPeripheralManager peripheral);
void DidAddService(CBPeripheralManager peripheral, CBService service, CBError error) { }
void DidStartAdvertising(CBPeripheralManager peripheral, CBError error) { }
void DidSubscribeToCharacteristic(CBPeripheralManager peripheral, CBCentral central, CBCharacteristic characteristic) { }
void DidUnsubscribeFromCharacteristic(CBPeripheralManager peripheral, CBCentral central, CBCharacteristic characteristic) { }
void IsReadyToUpdateSubscribers(CBPeripheralManager peripheral) { }
void DidReceiveReadRequest(CBPeripheralManager peripheral, CBATTRequest request) { }
void DidReceiveWriteRequests(CBPeripheralManager peripheral, CBATTRequest[] requests) { }
}
internal interface INativePeripheralManagerDelegate
{
void DidUpdateState(CBManagerState state) { }
void DidAddService(string serviceUUID, CBError error) { }
void DidStartAdvertising(CBError error) { }
void DidSubscribeToCharacteristic(SafeNativeCentralHandle central, string serviceUUID, string characteristicUUID) { }
void DidUnsubscribeFromCharacteristic(SafeNativeCentralHandle central, string serviceUUID, string characteristicUUID) { }
void IsReadyToUpdateSubscribers() { }
void DidReceiveReadRequest(SafeNativeATTRequestHandle request) { }
void DidReceiveWriteRequests(SafeNativeATTRequestsHandle requests) { }
}
internal interface IPeripheralManagerData
{
void AddCentral(CBCentral central);
CBCentral FindCentral(string centralId);
CBCharacteristic FindCharacteristic(string serviceUUID, string characteristicUUID);
}
public class CBPeripheralManager : CBManager, IPeripheralManagerData, INativePeripheralManagerDelegate, IDisposable
{
bool _disposed = false;
SafeNativePeripheralManagerHandle _handle;
NativePeripheralManagerProxy _nativePeripheralManagerProxy;
ICBPeripheralManagerDelegate _delegate;
public ICBPeripheralManagerDelegate Delegate
{
get => _delegate;
set
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_delegate = value;
}
}
///
/// ICBPeripheralManagerDelegate callbacks will be called in this context.
///
public SynchronizationContext CallbackContext { get; set; }
// key: centralId
Dictionary _centrals = new Dictionary();
Dictionary _services = new Dictionary();
HashSet _addingServiceUUIDs = new HashSet();
static readonly int s_maxATTRequests = 10;
Queue _attRequestDisposables = new Queue();
public CBPeripheralManager(ICBPeripheralManagerDelegate peripheralDelegate = null, CBPeripheralManagerInitOptions options = null)
{
if (options == null)
{
_handle = SafeNativePeripheralManagerHandle.Create();
}
else
{
using var optionsDict = options.ToNativeDictionary();
_handle = SafeNativePeripheralManagerHandle.Create(optionsDict.Handle);
}
Delegate = peripheralDelegate;
_nativePeripheralManagerProxy = new NativePeripheralManagerProxy(_handle, this);
CallbackContext = SynchronizationContext.Current;
}
void AddATTRequestDisposable(IDisposable disposable)
{
_attRequestDisposables.Enqueue(disposable);
while (_attRequestDisposables.Count > s_maxATTRequests)
{
_attRequestDisposables.Dequeue().Dispose();
}
}
public void AddService(CBMutableService service)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
if (_services.ContainsKey(service.UUID))
{
throw new ArgumentException($"Service {service} is already added.");
}
_addingServiceUUIDs.Add(service.UUID);
_services.Add(service.UUID, service);
_nativePeripheralManagerProxy.AddService(service.Handle);
}
public void RemoveService(CBMutableService service)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
if (!_services.ContainsKey(service.UUID))
{
throw new ArgumentException($"Service {service} is not added.");
}
_services.Remove(service.UUID);
_nativePeripheralManagerProxy.RemoveService(service.Handle);
}
public void RemoveAllServices()
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_services.Clear();
_nativePeripheralManagerProxy.RemoveAllServices();
}
public void StartAdvertising(StartAdvertisingOptions options = null)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheralManagerProxy.StartAdvertising(options);
}
public void StopAdvertising()
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheralManagerProxy.StopAdvertising();
}
public bool IsAdvertising
{
get
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
return _nativePeripheralManagerProxy.IsAdvertising;
}
}
public bool UpdateValue(byte[] value, CBMutableCharacteristic characteristic, CBCentral[] subscribedCentrals = null)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
SafeNativeCentralHandle[] subscribedCentralHandles = null;
if (subscribedCentrals != null)
{
subscribedCentralHandles = new SafeNativeCentralHandle[subscribedCentrals.Length];
for (int i = 0; i < subscribedCentrals.Length; i++)
{
subscribedCentralHandles[i] = subscribedCentrals[i].Handle;
}
}
return _nativePeripheralManagerProxy.UpdateValue(value, characteristic.Handle, subscribedCentralHandles);
}
public void RespondToRequest(CBATTRequest request, CBATTError result)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheralManagerProxy.RespondToRequest(request, result);
}
void IPeripheralManagerData.AddCentral(CBCentral central)
{
_centrals.Add(central.Identifier, central);
}
CBCentral IPeripheralManagerData.FindCentral(string centralId)
{
if (_centrals.TryGetValue(centralId, out var central))
{
return central;
}
return null;
}
CBCentral FindOrCreateCentral(SafeNativeCentralHandle centralHandle)
{
var central = new CBCentral(centralHandle);
if (_centrals.TryGetValue(central.Identifier, out var foundCentral))
{
central.Dispose();
return foundCentral;
}
_centrals.Add(central.Identifier, central);
return central;
}
CBCharacteristic IPeripheralManagerData.FindCharacteristic(string serviceUUID, string characteristicUUID)
{
if (_services.TryGetValue(serviceUUID, out var service))
{
return service.FindCharacteristic(characteristicUUID);
}
return null;
}
void INativePeripheralManagerDelegate.DidUpdateState(CBManagerState state)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
State = state;
_delegate?.DidUpdateState(this);
}, null);
}
void INativePeripheralManagerDelegate.DidAddService(string serviceUUID, CBError error)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
if (!_addingServiceUUIDs.Remove(serviceUUID))
{
return;
}
_delegate?.DidAddService(this, _services[serviceUUID], error);
}, null);
}
void INativePeripheralManagerDelegate.DidStartAdvertising(CBError error)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
_delegate?.DidStartAdvertising(this, error);
}, null);
}
void INativePeripheralManagerDelegate.DidSubscribeToCharacteristic(SafeNativeCentralHandle centralHandle, string serviceUUID, string characteristicUUID)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var central = FindOrCreateCentral(centralHandle);
var characteristic = ((IPeripheralManagerData)this).FindCharacteristic(serviceUUID, characteristicUUID);
_delegate?.DidSubscribeToCharacteristic(this, central, characteristic);
}, null);
}
void INativePeripheralManagerDelegate.DidUnsubscribeFromCharacteristic(SafeNativeCentralHandle centralHandle, string serviceUUID, string characteristicUUID)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var central = FindOrCreateCentral(centralHandle);
var characteristic = ((IPeripheralManagerData)this).FindCharacteristic(serviceUUID, characteristicUUID);
_delegate?.DidUnsubscribeFromCharacteristic(this, central, characteristic);
}, null);
}
void INativePeripheralManagerDelegate.IsReadyToUpdateSubscribers()
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
_delegate?.IsReadyToUpdateSubscribers(this);
}, null);
}
void INativePeripheralManagerDelegate.DidReceiveReadRequest(SafeNativeATTRequestHandle requestHandle)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var request = new CBATTRequest(requestHandle, new NativeATTRequestProxy(requestHandle, this));
_delegate?.DidReceiveReadRequest(this, request);
AddATTRequestDisposable(request);
}, null);
}
void INativePeripheralManagerDelegate.DidReceiveWriteRequests(SafeNativeATTRequestsHandle requestsHandle)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var requests = new CBATTRequests(requestsHandle, new NativeATTRequestsProxy(requestsHandle, this));
_delegate?.DidReceiveWriteRequests(this, requests.Requests);
AddATTRequestDisposable(requests);
}, null);
}
public void Dispose()
{
if (_disposed) return;
_nativePeripheralManagerProxy.Dispose();
_handle?.Dispose();
foreach (var central in _centrals.Values)
{
central.Dispose();
}
while (_attRequestDisposables.Count > 0)
{
_attRequestDisposables.Dequeue().Dispose();
}
_disposed = true;
}
}
}