docs: function parseBeacons (beaconStreamWithPreAmble, ecdhForLocalDevice,
                         addressBookCallback) {
    if (beaconStreamWithPreAmble == null) {
      return null;
    }
  
    var len = beaconStreamWithPreAmble.length;
  
    // Ensure that is an ECDH secp256k1 public key
    var pubKe = beaconStreamWithPreAmble.slice(0, module.exports.PUBLIC_KEY_SIZE);
    if (pubKe.length !== module.exports.PUBLIC_KEY_SIZE) {
      throw new Error(
        'Preamble public key must be from ECDH secp256k1'
      );
    }
  
    // Ensure that expiration is 64-bit integer
    var expirationBuffer = beaconStreamWithPreAmble
      .slice(module.exports.PUBLIC_KEY_SIZE,
        module.exports.PUBLIC_KEY_SIZE + module.exports.EXPIRATION_SIZE);
    if (expirationBuffer.length !== module.exports.EXPIRATION_SIZE) {
      throw new Error('Preamble expiration must be a 64 bit integer');
    }
  
    // Ensure within range
    var expiration = Long.fromBits(
      expirationBuffer.readInt32BE(4),
      expirationBuffer.readInt32BE(0)).toNumber();
    var now = Date.now();
    if (expiration < now || expiration > now + module.exports.ONE_DAY) {
      throw new Error('Expiration out of range');
    }
  
    // Sey = ECDH(Ky.private, PubKe)
    var sey = ecdhForLocalDevice.computeSecret(pubKe);
  
    // hkey = HKDF(SHA256, Sey, Expiration, 16)
    var hkey = HKDF(module.exports.SHA256, sey, expirationBuffer)
      .derive('', module.exports.AES_128_KEY_SIZE);
  
    for (var i = module.exports.PUBLIC_KEY_SIZE + module.exports.EXPIRATION_SIZE;
         i < len; i += module.exports.BEACON_SIZE) {
      // encryptedBeaconKeyId = beaconStream.read(48)
      var encryptedBeaconKeyId =
        beaconStreamWithPreAmble.slice(i, i + module.exports.BEACON_SIZE);
      if (encryptedBeaconKeyId.length !== module.exports.BEACON_SIZE) {
        throw new Error('Malformed encrypted beacon key ID');
      }
  
      // UnencryptedKeyId = AESDecrypt(GCM, HKey, 0, 128,
      // encryptedBeaconKeyId.slice(0, 32))
      var unencryptedKeyId;
      try {
        var aes = crypto.createDecipher(module.exports.GCM, hkey);
        unencryptedKeyId =
          Buffer.concat(
            [aes.update(encryptedBeaconKeyId.slice(0,
                                          module.exports.ENCRYPTED_KEY_ID_SIZE)),
              aes.final()]);
      } catch (e) {
        // Decryption failed due to bad decrypt, this is expected
        var BAD_DECRYPT_ERROR = 'error:06065064:digital envelope routines:' +
          'EVP_DecryptFinal_ex:bad decrypt';
        if (e.message === BAD_DECRYPT_ERROR) {
          continue;
        }
        throw e;
      }
      // PubKx = addressBook.get(UnencryptedKeyId)
      var pubKx = addressBookCallback(unencryptedKeyId);
  
      if (!pubKx) {
        // Device that claims to have sent the announcement is not recognized
        // Once we find a matching beacon we stop, even if the sender is
        // unrecognized
  
        // Until we add in GCM support the previous comment isn't true. For
        // now if we don't have a match it could be because the sender is
        // not trusted by us or it could be because the token wasn't for us
        // and we decrypted gibberish.
        continue;
      }
  
      // BeaconHmac = encryptedBeaconKeyId.slice(32, 48)
      var beaconHmac =
        encryptedBeaconKeyId.slice(module.exports.ENCRYPTED_KEY_ID_SIZE,
                                   module.exports.ENCRYPTED_KEY_ID_SIZE +
                                    module.exports.BEACON_HMAC_SIZE);
  
      // Sxy = ECDH(Ky.private(), PubKx)
      var sxy = ecdhForLocalDevice.computeSecret(pubKx);
  
      // HKxy = HKDF(SHA256, Sxy, Expiration, 32)
      var hkxy = HKDF(module.exports.SHA256, sxy, expirationBuffer)
        .derive('', module.exports.SHA256_HMAC_KEY_SIZE);
  
      // BeaconHmac.equals(HMAC(SHA256, HKxy, Expiration).first(16)
      var otherBeaconHmac = crypto.createHmac('sha256', hkxy)
        .update(expirationBuffer)
        .digest()
        .slice(0, module.exports.TRUNCATED_HASH_SIZE);
  
      // if (beaconHmac.equals(otherBeaconHmac)) {
      if (beaconHmac.compare(otherBeaconHmac) === 0) {
        return {
          preAmble: beaconStreamWithPreAmble.slice(0,
            module.exports.PUBLIC_KEY_SIZE +
            module.exports.EXPIRATION_SIZE),
          unencryptedKeyId: unencryptedKeyId,
          encryptedBeaconKeyId: encryptedBeaconKeyId
        };
      }
    }
  
    return null;
  }