result static method

Map<String, dynamic> result(
  1. String action,
  2. Object? raw, {
  3. int deviceLimit = maxDevices,
  4. int valueLimit = maxValueBytes,
})

Selects known fields only, and validates all native/provider output.

Implementation

static Map<String, dynamic> result(
  String action,
  Object? raw, {
  int deviceLimit = maxDevices,
  int valueLimit = maxValueBytes,
}) {
  if (raw is! Map) throw const FormatException('Invalid BLE result.');
  bool flag(Map value, String key) {
    if (value[key] is! bool) {
      throw const FormatException('Invalid BLE status.');
    }
    return value[key] as bool;
  }

  Map<String, dynamic> characteristic(Map value) => {
    'uuid': uuid(value['uuid']),
    for (final key in [
      'read',
      'write',
      'writeWithoutResponse',
      'notify',
      'indicate',
    ])
      key: flag(value, key),
  };
  switch (action) {
    case 'bluetooth.getState':
      return {
        for (final key in ['available', 'enabled', 'permissionGranted'])
          key: flag(raw, key),
      };
    case 'bluetooth.scan':
      final devices = raw['devices'];
      if (devices is! List || devices.length > deviceLimit) {
        throw const FormatException('Too many BLE devices.');
      }
      final seen = <String>{};
      return {
        'devices': devices
            .map((value) {
              if (value is! Map) {
                throw const FormatException('Invalid BLE device.');
              }
              final id = deviceId(value['deviceId']);
              final name = value['name'];
              final rssi = value['rssi'];
              if (!seen.add(id) ||
                  name is! String ||
                  name.length > 80 ||
                  RegExp(r'[\x00-\x1f\x7f-\x9f]').hasMatch(name) ||
                  rssi is! int ||
                  rssi < -127 ||
                  rssi > 20) {
                throw const FormatException('Invalid BLE device metadata.');
              }
              return {
                'deviceId': id,
                'name': name,
                'rssi': rssi,
                'serviceUuids': uuids(value['serviceUuids'] ?? const []),
              };
            })
            .toList(growable: false),
      };
    case 'bluetooth.connect':
      return {
        'deviceId': deviceId(raw['deviceId']),
        'connected': flag(raw, 'connected'),
      };
    case 'bluetooth.discoverServices':
      final services = raw['services'];
      if (services is! List || services.length > 32) {
        throw const FormatException('Too many BLE services.');
      }
      var count = 0;
      return {
        'services': services
            .map((value) {
              if (value is! Map) {
                throw const FormatException('Invalid BLE service.');
              }
              final chars = value['characteristics'];
              if (chars is! List ||
                  chars.length > 64 ||
                  (count += chars.length) > 256) {
                throw const FormatException('Too many BLE characteristics.');
              }
              return {
                'uuid': uuid(value['uuid']),
                'characteristics': chars
                    .map((v) {
                      if (v is! Map) {
                        throw const FormatException(
                          'Invalid BLE characteristic.',
                        );
                      }
                      return characteristic(v);
                    })
                    .toList(growable: false),
              };
            })
            .toList(growable: false),
      };
    case 'bluetooth.read':
    case 'bluetooth.notification':
      return {
        'serviceUuid': uuid(raw['serviceUuid']),
        'characteristicUuid': uuid(raw['characteristicUuid']),
        'valueHex': hex(raw['valueHex'], limit: valueLimit),
      };
    case 'bluetooth.write':
      final bytes = raw['bytesWritten'];
      if (bytes is! int || bytes < 1 || bytes > maxWriteBytes) {
        throw const FormatException('Invalid BLE write result.');
      }
      return {'bytesWritten': bytes};
    case 'bluetooth.subscribe':
    case 'bluetooth.unsubscribe':
      return {'subscribed': flag(raw, 'subscribed')};
    case 'bluetooth.stopScan':
    case 'bluetooth.disconnect':
      return {'changed': flag(raw, 'changed')};
    default:
      throw const FormatException('Unknown BLE result.');
  }
}