Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: Fully Expanded Passkey Objects + JSDoc types #6

Open
2 tasks done
coolaj86 opened this issue Oct 2, 2024 · 1 comment
Open
2 tasks done

doc: Fully Expanded Passkey Objects + JSDoc types #6

coolaj86 opened this issue Oct 2, 2024 · 1 comment

Comments

@coolaj86
Copy link
Contributor

coolaj86 commented Oct 2, 2024

TODO:

  • Decode PEM to JWK
  • correct CBOR parser for credentialId

Partial at go-webauthn/webauthn#291 (comment)

aaguid lookup: https://passkeydeveloper.github.io/passkey-authenticator-aaguids/explorer/?combined

Passkey (WebAuthn Credential) Registration (Creation)

./fixtures/apple-m2-01-credential-creation-response.json (fully decoded):

{
  "authenticatorAttachment": "platform",

  "id": "BvvM60zMdJwPG8Z2eeBjXu9nyd8",
  "rawId": "BvvM60zMdJwPG8Z2eeBjXu9nyd8",
  "rawIdHex": "06fbcceb4ccc749c0f1bc67679e0635eef67c9df",

  "response": {

    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYvyNDyj998pf0HSG0UpSMrRAgHVyp9ZDvZgDE0HEs2jBdAAAAAPv8MAcVTk7MjAtuAgVX170AFAb7zOtMzHScDxvGdnngY17vZ8nfpQECAyYgASFYINbKXNDEO9D2GzLehw5365SM4scbsysvT4DsOVZkCZqHIlggr6WD8AitmkMfu8UIs-QgwdGtjUmkMrhz_lhP-jxMG-g",
    "attestationObjectHex": "a363666d74646e6f6e656761747453746d74a06861757468446174615898bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda305d00000000fbfc3007154e4ecc8c0b6e020557d7bd001406fbcceb4ccc749c0f1bc67679e0635eef67c9dfa5010203262001215820d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87225820afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8",
    "attestation": {
      "fmt": "none",
      "attStmt": {},
      "authData": "bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda305d00000000fbfc3007154e4ecc8c0b6e020557d7bd001406fbcceb4ccc749c0f1bc67679e0635eef67c9dfa5010203262001215820d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87225820afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8",
      "authenticator": {
        "rpidHash": "bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda30",
        "isUserPresent": true,
        "userVerified": true,
        "isMultiDeviceCredential": true,
        "isBackedUp": true,
        "hasAttestedCredential": true,
        "hasExtensionData": false,
        "signCount": 0,
        "attestedCredentialData": "fbfc3007154e4ecc8c0b6e020557d7bd001406fbcceb4ccc749c0f1bc67679e0635eef67c9df",
        "attestedCredential": {
          "aaguid": "fbfc3007-154e-4ecc-8c0b-6e020557d7bd",
          "credentialId": "06fbcceb4ccc749c0f1bc67679e0635eef67c9df",
          "publicKeyCBOR": "a5010203262001215820d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87225820afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8",
          "publicKeyCOSE": {
            "1": 2,
            "3": -7,
            "-1": 1,
            "-2": "6761747453746d74a06861757468446174615898bf2343ca3f7df297f41d21b4",
            "-3": "ad10201d5ca9f590ef6600c4d0712cda305d00000000fbfc3007154e4ecc8c0b"
          },
          "publicKeyJWK": {
            "kty": "EC",
            "alg": "ES256",
            "x": "",
            "y": ""
          }
        },
        "extensions": null
      }
    },

    "authenticatorData": "vyNDyj998pf0HSG0UpSMrRAgHVyp9ZDvZgDE0HEs2jBdAAAAAPv8MAcVTk7MjAtuAgVX170AFAb7zOtMzHScDxvGdnngY17vZ8nfpQECAyYgASFYINbKXNDEO9D2GzLehw5365SM4scbsysvT4DsOVZkCZqHIlggr6WD8AitmkMfu8UIs-QgwdGtjUmkMrhz_lhP-jxMG-g",
    "authenticatorDataHex": "bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda305d00000000fbfc3007154e4ecc8c0b6e020557d7bd001406fbcceb4ccc749c0f1bc67679e0635eef67c9dfa5010203262001215820d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87225820afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8",
    "authenticator": {
      "rpidHash": "bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda30",
      "isUserPresent": true,
      "userVerified": true,
      "isMultiDeviceCredential": true,
      "isBackedUp": true,
      "hasAttestedCredential": true,
      "hasExtensionData": false,
      "signCount": 0,
      "attestedCredentialData": "fbfc3007154e4ecc8c0b6e020557d7bd001406fbcceb4ccc749c0f1bc67679e0635eef67c9df",
      "attestedCredential": {
        "aaguid": "fbfc3007-154e-4ecc-8c0b-6e020557d7bd",
        "credentialId": "06fbcceb4ccc749c0f1bc67679e0635eef67c9df",
        "publicKeyCBOR": "a5010203262001215820d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87225820afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8",
        "publicKey": {
          "1": 2,
          "3": -7,
          "-1": 1,
          "-2": "21b452948cad10201d5ca9f590ef6600c4d0712cda305d00000000fbfc300715",
          "-3": "8c0b6e020557d7bd001406fbcceb4ccc749c0f1bc67679e0635eef67c9dfa501"
        },
        "publicKeyJWK": {
          "kty": "EC",
          "alg": "ES256",
          "crv": "P-256",
          "x": "IbRSlIytECAdXKn1kO9mAMTQcSzaMF0AAAAA-_wwBxU",
          "y": "jAtuAgVX170AFAb7zOtMzHScDxvGdnngY17vZ8nfpQE"
        }
      }
    },

    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidlZuWVVhWC1XMVZkcUtCWjdEdE5jcWdjR1MwbUVyNktCQzdmVmE5QkRHYyIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWwucG9ja2V0aWQuYXBwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ",
    "clientDataJSONHex": "7b2274797065223a22776562617574686e2e637265617465222c226368616c6c656e6765223a2276566e595561582d57315664714b425a3744744e637167634753306d4572364b4243376656613942444763222c226f726967696e223a2268747470733a2f2f6c6f63616c2e706f636b657469642e617070222c2263726f73734f726967696e223a66616c73657d",
    "clientDataJSONString": "{\"type\":\"webauthn.create\",\"challenge\":\"vVnYUaX-W1VdqKBZ7DtNcqgcGS0mEr6KBC7fVa9BDGc\",\"origin\":\"https://local.pocketid.app\",\"crossOrigin\":false}",
    "clientData": {
      "type": "webauthn.create",
      "challenge": "vVnYUaX-W1VdqKBZ7DtNcqgcGS0mEr6KBC7fVa9BDGc",
      "origin": "https://local.pocketid.app",
      "crossOrigin": false
    },

    "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1spc0MQ70PYbMt6HDnfrlIzixxuzKy9PgOw5VmQJmoevpYPwCK2aQx-7xQiz5CDB0a2NSaQyuHP-WE_6PEwb6A",
    "publicKeyHex": "3059301306072a8648ce3d020106082a8648ce3d03010703420004d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8",
    "publicKeyDer": "3059301306072a8648ce3d020106082a8648ce3d03010703420004d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8",

    "publicKeyAlgorithm": -7,
    "publicKeyAlgo": "ES256",
    "publicKeyAlgorithmName": "ES256",

    "transports": [
      "hybrid",
      "internal"
    ]

  },

  "type": "public-key"
}

Passkey (WebAuthn Credential) Authentication (Attestation)

./fixtures/apple-m2-02-credential-request-response.json (fully decoded):

{
  "authenticatorAttachment": "platform",

  "id": "BvvM60zMdJwPG8Z2eeBjXu9nyd8",
  "rawId": "BvvM60zMdJwPG8Z2eeBjXu9nyd8",
  "rawIdHex": "06fbcceb4ccc749c0f1bc67679e0635eef67c9df",

  "response": {

    "authenticatorData": "vyNDyj998pf0HSG0UpSMrRAgHVyp9ZDvZgDE0HEs2jAdAAAAAA",
    "authenticatorDataHex": "bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda301d00000000",
    "authenticator": {
      "rpidHash": "bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda30",
      "isUserPresent": true,
      "userVerified": true,
      "isMultiDeviceCredential": true,
      "isBackedUp": true,
      "hasAttestedCredential": false,
      "hasExtensionData": false,
      "signCount": 0,
      "attestedCredentialData": null,
      "attestedCredential": null
    },

    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiY0pDM1Bwa3pLOVBQeWFwRDJOQXVlTjBqVmltMUJPLU1iWHh6NDRXMWNUdyIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWwucG9ja2V0aWQuYXBwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ",
    "clientDataJSONHex": "7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22634a433350706b7a4b39505079617044324e4175654e306a56696d31424f2d4d6258787a34345731635477222c226f726967696e223a2268747470733a2f2f6c6f63616c2e706f636b657469642e617070222c2263726f73734f726967696e223a66616c73657d",
    "clientDataJSONString": "{\"type\":\"webauthn.get\",\"challenge\":\"cJC3PpkzK9PPyapD2NAueN0jVim1BO-MbXxz44W1cTw\",\"origin\":\"https://local.pocketid.app\",\"crossOrigin\":false}",
    "clientData": {
      "type": "webauthn.get",
      "challenge": "cJC3PpkzK9PPyapD2NAueN0jVim1BO-MbXxz44W1cTw",
      "origin": "https://local.pocketid.app",
      "crossOrigin": false
    },

    "hash": "e2eacf22fcb185ebab56d30ff6c3a48e370b55a5a7bb237cc891c57f4b145f13",

    "signature": "MEQCIEbaFzFqEPDuzy_CqEj4WcTGhIBw10YhHXS9H9RDK7uqAiAHTR4wgpiwITNvfGZCijobw66ovUnzeh2r8mdzl4al-Q",
    "signatureHex": "3044022046da17316a10f0eecf2fc2a848f859c4c6848070d746211d74bd1fd4432bbbaa0220074d1e308298b021336f7c66428a3a1bc3aea8bd49f37a1dabf267739786a5f9"

  },

  "type": "public-key"
}

Code Examples

Registration

'use strict';

let Passkey = require('$/src/lib/passkey.js');
let Bytes = require('$/src/lib/bytes.js');

// ./fixtures/apple-m2-01-credential-creation-response.json
let regResp = {
  authenticatorAttachment: 'platform',
  id: 'BvvM60zMdJwPG8Z2eeBjXu9nyd8',
  rawId: 'BvvM60zMdJwPG8Z2eeBjXu9nyd8',
  rawIdHex: '06fbcceb4ccc749c0f1bc67679e0635eef67c9df',
  response: {
    attestationObject:
      'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYvyNDyj998pf0HSG0UpSMrRAgHVyp9ZDvZgDE0HEs2jBdAAAAAPv8MAcVTk7MjAtuAgVX170AFAb7zOtMzHScDxvGdnngY17vZ8nfpQECAyYgASFYINbKXNDEO9D2GzLehw5365SM4scbsysvT4DsOVZkCZqHIlggr6WD8AitmkMfu8UIs-QgwdGtjUmkMrhz_lhP-jxMG-g',
    attestationObjectHex:
      'a363666d74646e6f6e656761747453746d74a06861757468446174615898bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda305d00000000fbfc3007154e4ecc8c0b6e020557d7bd001406fbcceb4ccc749c0f1bc67679e0635eef67c9dfa5010203262001215820d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87225820afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8',
    authenticatorData:
      'vyNDyj998pf0HSG0UpSMrRAgHVyp9ZDvZgDE0HEs2jBdAAAAAPv8MAcVTk7MjAtuAgVX170AFAb7zOtMzHScDxvGdnngY17vZ8nfpQECAyYgASFYINbKXNDEO9D2GzLehw5365SM4scbsysvT4DsOVZkCZqHIlggr6WD8AitmkMfu8UIs-QgwdGtjUmkMrhz_lhP-jxMG-g',
    authenticatorDataHex:
      'bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda305d00000000fbfc3007154e4ecc8c0b6e020557d7bd001406fbcceb4ccc749c0f1bc67679e0635eef67c9dfa5010203262001215820d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87225820afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8',
    clientDataJSON:
      'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidlZuWVVhWC1XMVZkcUtCWjdEdE5jcWdjR1MwbUVyNktCQzdmVmE5QkRHYyIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWwucG9ja2V0aWQuYXBwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ',
    clientDataJSONHex:
      '7b2274797065223a22776562617574686e2e637265617465222c226368616c6c656e6765223a2276566e595561582d57315664714b425a3744744e637167634753306d4572364b4243376656613942444763222c226f726967696e223a2268747470733a2f2f6c6f63616c2e706f636b657469642e617070222c2263726f73734f726967696e223a66616c73657d',
    publicKey:
      'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1spc0MQ70PYbMt6HDnfrlIzixxuzKy9PgOw5VmQJmoevpYPwCK2aQx-7xQiz5CDB0a2NSaQyuHP-WE_6PEwb6A',
    publicKeyHex:
      '3059301306072a8648ce3d020106082a8648ce3d03010703420004d6ca5cd0c43bd0f61b32de870e77eb948ce2c71bb32b2f4f80ec395664099a87afa583f008ad9a431fbbc508b3e420c1d1ad8d49a432b873fe584ffa3c4c1be8',
    publicKeyAlgorithm: -7,
    publicKeyAlgorithmName: 'ES256',
    transports: ['hybrid', 'internal'],
  },
  type: 'public-key',
};

let resp = Passkey.reg.parse(regResp);
Object.assign(regResp.response, resp);

function bytesToHex(key, val) {
  if (val instanceof Uint8Array) {
    return Bytes.bytesToHex(val);
  }
  return val;
}

let json = JSON.stringify(regResp, bytesToHex, 2);
console.log(json);

Authentication

'use strict';

let Passkey = require('$/src/lib/passkey.js');
let Bytes = require('$/src/lib/bytes.js');

// ./fixtures/apple-m2-02-credential-request-response.json
let authnResp = {
  authenticatorAttachment: 'platform',
  id: 'BvvM60zMdJwPG8Z2eeBjXu9nyd8',
  rawId: 'BvvM60zMdJwPG8Z2eeBjXu9nyd8',
  rawIdHex: '06fbcceb4ccc749c0f1bc67679e0635eef67c9df',
  response: {
    authenticatorData: 'vyNDyj998pf0HSG0UpSMrRAgHVyp9ZDvZgDE0HEs2jAdAAAAAA',
    authenticatorDataHex:
      'bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda301d00000000',
    clientDataJSON:
      'eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiY0pDM1Bwa3pLOVBQeWFwRDJOQXVlTjBqVmltMUJPLU1iWHh6NDRXMWNUdyIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWwucG9ja2V0aWQuYXBwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ',
    clientDataJSONHex:
      '7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22634a433350706b7a4b39505079617044324e4175654e306a56696d31424f2d4d6258787a34345731635477222c226f726967696e223a2268747470733a2f2f6c6f63616c2e706f636b657469642e617070222c2263726f73734f726967696e223a66616c73657d',
    signature:
      'MEQCIEbaFzFqEPDuzy_CqEj4WcTGhIBw10YhHXS9H9RDK7uqAiAHTR4wgpiwITNvfGZCijobw66ovUnzeh2r8mdzl4al-Q',
    signatureHex:
      '3044022046da17316a10f0eecf2fc2a848f859c4c6848070d746211d74bd1fd4432bbbaa0220074d1e308298b021336f7c66428a3a1bc3aea8bd49f37a1dabf267739786a5f9',
  },
  type: 'public-key',
};

let resp = Passkey.authn.parse(authnResp);
Object.assign(authnResp.response, resp);

function bytesToHex(key, val) {
  if (val instanceof Uint8Array) {
    return Bytes.bytesToHex(val);
  }
  return val;
}

let json = JSON.stringify(authnResp, bytesToHex, 2);
console.log(json);
@coolaj86
Copy link
Contributor Author

coolaj86 commented Oct 4, 2024

Simple CBOR Parser

Example

import CBOR from './cbor.js';

let cborHex = 'bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda301d00000000';
let cborBytes = Bytes.hexToBytes(cbor);
let data = CBOR.parse(cborBytes);
console.log(data);
let CBOR = require('./cbor.js');

let cborHex = 'bf2343ca3f7df297f41d21b452948cad10201d5ca9f590ef6600c4d0712cda301d00000000';
let cborBytes = Bytes.hexToBytes(cbor);
let data = CBOR.parse(cborBytes);
console.log(data);

Parser

'use strict';
// jshint bitwise: false

/**
 * @license CC0-1.0
 *
 * @root/cbor-parser.js - for WebAuthn Passkey data
 *
 * Authored in 2024 by AJ ONeal
 * To the extent possible under law, the author(s) have dedicated all copyright
 * and related and neighboring rights to this software to the public domain
 * worldwide. This software is distributed without any warranty.
 *
 * You should have received a copy of the CC0 Public Domain Dedication along with
 * this software. If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
 */

// for ESM
let CBOR = {};

// for CommonJS:
//let CBOR = module.exports;

CBOR.parse = function (bytes) {
  let cbor = CBOR.create(bytes);
  let result = cbor._parse();
  return result;
};

CBOR.create = function (data) {
  let cbor = {};

  cbor._data = new DataView(data.buffer, data.byteOffset, data.byteLength);
  cbor._offset = 0;

  cbor._parse = function () {
    let initialByte = cbor._readByte();
    let majorType = initialByte >> 5; // First 3 bits
    let additionalInfo = initialByte & 0x1f; // Last 5 bits
    return cbor._parseItem(majorType, additionalInfo);
  };

  cbor._parseItem = function (majorType, additionalInfo) {
    switch (majorType) {
      case 0:
        return cbor._parseUnsignedInt(additionalInfo);
      case 1:
        return cbor._parseNegativeInt(additionalInfo);
      case 2:
        return cbor._parseByteString(additionalInfo);
      case 3:
        return cbor._parseTextString(additionalInfo);
      case 4:
        return cbor._parseArray(additionalInfo);
      case 5:
        return cbor._parseMap(additionalInfo);
      case 6:
        return cbor._parseTag(additionalInfo);
      case 7:
        return cbor._parseSimpleValue(additionalInfo);
      default:
        throw new Error(`Unsupported major type: ${majorType}`);
    }
  };

  cbor._parseUnsignedInt = function (additionalInfo) {
    return cbor._decodeLength(additionalInfo);
  };

  cbor._parseNegativeInt = function (additionalInfo) {
    let unsignedValue = cbor._decodeLength(additionalInfo);
    return -1 - unsignedValue;
  };

  cbor._parseByteString = function (additionalInfo) {
    let length = cbor._decodeLength(additionalInfo);
    return cbor._readBytes(length);
  };

  cbor._parseTextString = function (additionalInfo) {
    let length = cbor._decodeLength(additionalInfo);
    let textBytes = cbor._readBytes(length);
    return new TextDecoder().decode(textBytes);
  };

  cbor._parseArray = function (additionalInfo) {
    if (additionalInfo === 31) {
      // Indefinite length array
      let array = [];
      while (cbor._peekByte() !== 0xff) {
        array.push(cbor._parse());
      }
      cbor._readByte(); // consume stop code
      return array;
    }
    let length = cbor._decodeLength(additionalInfo);
    let array = [];
    for (let i = 0; i < length; i += 1) {
      array.push(cbor._parse());
    }
    return array;
  };

  cbor._parseMap = function (additionalInfo) {
    if (additionalInfo === 31) {
      // Indefinite length map
      let map = {};
      while (cbor._peekByte() !== 0xff) {
        let key = cbor._parse();
        let value = cbor._parse();
        map[key] = value;
      }
      cbor._readByte(); // consume stop code
      return map;
    }
    let length = cbor._decodeLength(additionalInfo);
    let map = {};
    for (let i = 0; i < length; i += 1) {
      let key = cbor._parse();
      let value = cbor._parse();
      map[key] = value;
    }
    return map;
  };

  // Parse tags (e.g. for big numbers)
  cbor._parseTag = function (additionalInfo) {
    let tag = cbor._decodeLength(additionalInfo);
    let value = cbor._parse();
    if (tag === 18) {
      // Special type for positive bignum
      return value;
    }
    return { tag, value };
  };

  cbor._parseSimpleValue = function (additionalInfo) {
    switch (additionalInfo) {
      case 20:
        return false;
      case 21:
        return true;
      case 22:
        return null;
      case 23:
        return undefined;
      case 24:
        return cbor._readByte(); // simple value
      case 25:
        return cbor._parseFloat16();
      case 26:
        return cbor._parseFloat32();
      case 27:
        return cbor._parseFloat64();
      case 31:
        return null; // Indefinite-length item (handled by array/map)
      default:
        throw new Error(`Unsupported simple value: ${additionalInfo}`);
    }
  };

  cbor._decodeLength = function (additionalInfo) {
    if (additionalInfo < 24) {
      return additionalInfo;
    }
    if (additionalInfo === 24) {
      return cbor._readByte();
    }
    if (additionalInfo === 25) {
      return cbor._readUint16();
    }
    if (additionalInfo === 26) {
      return cbor._readUint32();
    }
    if (additionalInfo === 27) {
      return cbor._readUint64();
    }
    throw new Error(`Unsupported additional info: ${additionalInfo}`);
  };

  cbor._readUint16 = function () {
    let val = cbor._data.getUint16(cbor._offset);
    cbor._offset += 2;
    return val;
  };

  cbor._readUint32 = function () {
    let val = cbor._data.getUint32(cbor._offset);
    cbor._offset += 4;
    return val;
  };

  cbor._readUint64 = function () {
    let high = cbor._data.getUint32(cbor._offset);
    let low = cbor._data.getUint32(cbor._offset + 4);
    cbor._offset += 8;
    return (BigInt(high) << 32n) | BigInt(low);
  };

  cbor._readByte = function () {
    let b = cbor._data.getUint8(cbor._offset);
    cbor._offset += 1;
    return b;
  };

  cbor._peekByte = function () {
    return cbor._data.getUint8(cbor._offset);
  };

  cbor._readBytes = function (length) {
    let bytes = new Uint8Array(cbor._data.buffer, cbor._offset, length);
    cbor._offset += length;
    return bytes;
  };

  cbor._parseFloat16 = function () {
    let u16 = cbor._readUint16();

    let exponentBits = u16 >> 10;
    let exponent = exponentBits & 0x1f;

    let mantissa = u16 & 0x3ff;

    // Check for subnormal numbers (exponent = 0) or zero
    if (exponent === 0) {
      // Subnormal number or zero: exponent is implicitly -14, no leading 1 in mantissa
      let f16 = mantissa * 5.96e-8; // 2^(-24)
      return f16;
    }

    // Check for special cases (exponent = 31): infinity or NaN
    if (exponent === 31) {
      if (mantissa !== 0) {
        return NaN;
      }
      return Infinity;
    }

    // Normalize the fraction (add the implicit leading 1 for normalized numbers)
    let normalizedMantissa = mantissa / 1024 + 1;
    // Adjust the exponent (bias of 15)
    let adjustedExponent = exponent - 15;
    // Calculate the result as: (1 + fraction) * 2^(exponent - bias)
    let f16 = normalizedMantissa * 2 ** adjustedExponent;

    return f16;
  };

  cbor._parseFloat32 = function () {
    return cbor._data.getFloat32(cbor._offset, false);
  };

  cbor._parseFloat64 = function () {
    let val = cbor._data.getFloat64(cbor._offset, false);
    cbor._offset += 8;
    return val;
  };

  return cbor;
};

// for ESM:
export default CBOR;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant