@sd-jwt/jwt-status-list 0.6.2-next.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ StatusList: () => StatusList,
34
+ createHeaderAndPayload: () => createHeaderAndPayload,
35
+ getListFromStatusListJWT: () => getListFromStatusListJWT,
36
+ getStatusListFromJWT: () => getStatusListFromJWT
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+
40
+ // src/status-list.ts
41
+ var import_pako = require("pako");
42
+ var import_base64url = __toESM(require("base64url"));
43
+ var StatusList = class _StatusList {
44
+ /**
45
+ * Create a new StatusListManager instance.
46
+ * @param statusList
47
+ * @param bitsPerStatus
48
+ */
49
+ constructor(statusList, bitsPerStatus) {
50
+ if (![1, 2, 4, 8].includes(bitsPerStatus)) {
51
+ throw new Error("bitsPerStatus must be 1, 2, 4, or 8");
52
+ }
53
+ for (let i = 0; i < statusList.length; i++) {
54
+ if (statusList[i] > 2 ** bitsPerStatus) {
55
+ throw Error(
56
+ `Status value out of range at index ${i} with value ${statusList[i]}`
57
+ );
58
+ }
59
+ }
60
+ this.statusList = statusList;
61
+ this.bitsPerStatus = bitsPerStatus;
62
+ this.totalStatuses = statusList.length;
63
+ }
64
+ /**
65
+ * Get the number of statuses.
66
+ * @returns
67
+ */
68
+ getBitsPerStatus() {
69
+ return this.bitsPerStatus;
70
+ }
71
+ /**
72
+ * Get the status at a specific index.
73
+ * @param index
74
+ * @returns
75
+ */
76
+ getStatus(index) {
77
+ if (index < 0 || index >= this.totalStatuses) {
78
+ throw new Error("Index out of bounds");
79
+ }
80
+ return this.statusList[index];
81
+ }
82
+ /**
83
+ * Set the status at a specific index.
84
+ * @param index
85
+ * @param value
86
+ */
87
+ setStatus(index, value) {
88
+ if (index < 0 || index >= this.totalStatuses) {
89
+ throw new Error("Index out of bounds");
90
+ }
91
+ this.statusList[index] = value;
92
+ }
93
+ /**
94
+ * Compress the status list.
95
+ * @returns
96
+ */
97
+ compressStatusList() {
98
+ const byteArray = this.encodeStatusList();
99
+ const compressed = (0, import_pako.deflate)(byteArray, { level: 9 });
100
+ return import_base64url.default.encode(compressed);
101
+ }
102
+ /**
103
+ * Decompress the compressed status list and return a new StatusList instance.
104
+ * @param compressed
105
+ * @param bitsPerStatus
106
+ * @returns
107
+ */
108
+ static decompressStatusList(compressed, bitsPerStatus) {
109
+ const decoded = new Uint8Array(
110
+ import_base64url.default.decode(compressed, "binary").split("").map((c) => c.charCodeAt(0))
111
+ );
112
+ try {
113
+ const decompressed = (0, import_pako.inflate)(decoded);
114
+ const statusList = _StatusList.decodeStatusList(
115
+ decompressed,
116
+ bitsPerStatus
117
+ );
118
+ return new _StatusList(statusList, bitsPerStatus);
119
+ } catch (err) {
120
+ throw new Error(`Decompression failed: ${err}`);
121
+ }
122
+ }
123
+ /**
124
+ * Encode the status list into a byte array.
125
+ * @returns
126
+ **/
127
+ encodeStatusList() {
128
+ const numBits = this.bitsPerStatus;
129
+ const numBytes = Math.ceil(this.totalStatuses * numBits / 8);
130
+ const byteArray = new Uint8Array(numBytes);
131
+ let byteIndex = 0;
132
+ let bitIndex = 0;
133
+ let currentByte = "";
134
+ for (let i = 0; i < this.totalStatuses; i++) {
135
+ const status = this.statusList[i];
136
+ currentByte = status.toString(2).padStart(numBits, "0") + currentByte;
137
+ bitIndex += numBits;
138
+ if (bitIndex >= 8 || i === this.totalStatuses - 1) {
139
+ if (i === this.totalStatuses - 1 && bitIndex % 8 !== 0) {
140
+ currentByte = currentByte.padStart(8, "0");
141
+ }
142
+ byteArray[byteIndex] = Number.parseInt(currentByte, 2);
143
+ currentByte = "";
144
+ bitIndex = 0;
145
+ byteIndex++;
146
+ }
147
+ }
148
+ return byteArray;
149
+ }
150
+ /**
151
+ * Decode the byte array into a status list.
152
+ * @param byteArray
153
+ * @param bitsPerStatus
154
+ * @returns
155
+ */
156
+ static decodeStatusList(byteArray, bitsPerStatus) {
157
+ const numBits = bitsPerStatus;
158
+ const totalStatuses = byteArray.length * 8 / numBits;
159
+ const statusList = new Array(totalStatuses);
160
+ let bitIndex = 0;
161
+ for (let i = 0; i < totalStatuses; i++) {
162
+ const byte = byteArray[Math.floor(i * numBits / 8)];
163
+ let byteString = byte.toString(2);
164
+ if (byteString.length < 8) {
165
+ byteString = "0".repeat(8 - byteString.length) + byteString;
166
+ }
167
+ const status = byteString.slice(bitIndex, bitIndex + numBits);
168
+ const group = Math.floor(i / (8 / numBits));
169
+ const indexInGroup = i % (8 / numBits);
170
+ const position = group * (8 / numBits) + (8 / numBits + -1 - indexInGroup);
171
+ statusList[position] = Number.parseInt(status, 2);
172
+ bitIndex = (bitIndex + numBits) % 8;
173
+ }
174
+ return statusList;
175
+ }
176
+ };
177
+
178
+ // src/status-list-jwt.ts
179
+ var import_base64url2 = __toESM(require("base64url"));
180
+ function decodeJwt(jwt) {
181
+ const parts = jwt.split(".");
182
+ return JSON.parse(import_base64url2.default.decode(parts[1]));
183
+ }
184
+ function createHeaderAndPayload(list, payload, header) {
185
+ if (!payload.iss) {
186
+ throw new Error("iss field is required");
187
+ }
188
+ if (!payload.sub) {
189
+ throw new Error("sub field is required");
190
+ }
191
+ if (!payload.iat) {
192
+ throw new Error("iat field is required");
193
+ }
194
+ header.typ = "statuslist+jwt";
195
+ payload.status_list = {
196
+ bits: list.getBitsPerStatus(),
197
+ lst: list.compressStatusList()
198
+ };
199
+ return { header, payload };
200
+ }
201
+ function getListFromStatusListJWT(jwt) {
202
+ const payload = decodeJwt(jwt);
203
+ const statusList = payload.status_list;
204
+ return StatusList.decompressStatusList(statusList.lst, statusList.bits);
205
+ }
206
+ function getStatusListFromJWT(jwt) {
207
+ const payload = decodeJwt(jwt);
208
+ return payload.status.status_list;
209
+ }
210
+ // Annotate the CommonJS export names for ESM import in node:
211
+ 0 && (module.exports = {
212
+ StatusList,
213
+ createHeaderAndPayload,
214
+ getListFromStatusListJWT,
215
+ getStatusListFromJWT
216
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,176 @@
1
+ // src/status-list.ts
2
+ import { deflate, inflate } from "pako";
3
+ import base64Url from "base64url";
4
+ var StatusList = class _StatusList {
5
+ /**
6
+ * Create a new StatusListManager instance.
7
+ * @param statusList
8
+ * @param bitsPerStatus
9
+ */
10
+ constructor(statusList, bitsPerStatus) {
11
+ if (![1, 2, 4, 8].includes(bitsPerStatus)) {
12
+ throw new Error("bitsPerStatus must be 1, 2, 4, or 8");
13
+ }
14
+ for (let i = 0; i < statusList.length; i++) {
15
+ if (statusList[i] > 2 ** bitsPerStatus) {
16
+ throw Error(
17
+ `Status value out of range at index ${i} with value ${statusList[i]}`
18
+ );
19
+ }
20
+ }
21
+ this.statusList = statusList;
22
+ this.bitsPerStatus = bitsPerStatus;
23
+ this.totalStatuses = statusList.length;
24
+ }
25
+ /**
26
+ * Get the number of statuses.
27
+ * @returns
28
+ */
29
+ getBitsPerStatus() {
30
+ return this.bitsPerStatus;
31
+ }
32
+ /**
33
+ * Get the status at a specific index.
34
+ * @param index
35
+ * @returns
36
+ */
37
+ getStatus(index) {
38
+ if (index < 0 || index >= this.totalStatuses) {
39
+ throw new Error("Index out of bounds");
40
+ }
41
+ return this.statusList[index];
42
+ }
43
+ /**
44
+ * Set the status at a specific index.
45
+ * @param index
46
+ * @param value
47
+ */
48
+ setStatus(index, value) {
49
+ if (index < 0 || index >= this.totalStatuses) {
50
+ throw new Error("Index out of bounds");
51
+ }
52
+ this.statusList[index] = value;
53
+ }
54
+ /**
55
+ * Compress the status list.
56
+ * @returns
57
+ */
58
+ compressStatusList() {
59
+ const byteArray = this.encodeStatusList();
60
+ const compressed = deflate(byteArray, { level: 9 });
61
+ return base64Url.encode(compressed);
62
+ }
63
+ /**
64
+ * Decompress the compressed status list and return a new StatusList instance.
65
+ * @param compressed
66
+ * @param bitsPerStatus
67
+ * @returns
68
+ */
69
+ static decompressStatusList(compressed, bitsPerStatus) {
70
+ const decoded = new Uint8Array(
71
+ base64Url.decode(compressed, "binary").split("").map((c) => c.charCodeAt(0))
72
+ );
73
+ try {
74
+ const decompressed = inflate(decoded);
75
+ const statusList = _StatusList.decodeStatusList(
76
+ decompressed,
77
+ bitsPerStatus
78
+ );
79
+ return new _StatusList(statusList, bitsPerStatus);
80
+ } catch (err) {
81
+ throw new Error(`Decompression failed: ${err}`);
82
+ }
83
+ }
84
+ /**
85
+ * Encode the status list into a byte array.
86
+ * @returns
87
+ **/
88
+ encodeStatusList() {
89
+ const numBits = this.bitsPerStatus;
90
+ const numBytes = Math.ceil(this.totalStatuses * numBits / 8);
91
+ const byteArray = new Uint8Array(numBytes);
92
+ let byteIndex = 0;
93
+ let bitIndex = 0;
94
+ let currentByte = "";
95
+ for (let i = 0; i < this.totalStatuses; i++) {
96
+ const status = this.statusList[i];
97
+ currentByte = status.toString(2).padStart(numBits, "0") + currentByte;
98
+ bitIndex += numBits;
99
+ if (bitIndex >= 8 || i === this.totalStatuses - 1) {
100
+ if (i === this.totalStatuses - 1 && bitIndex % 8 !== 0) {
101
+ currentByte = currentByte.padStart(8, "0");
102
+ }
103
+ byteArray[byteIndex] = Number.parseInt(currentByte, 2);
104
+ currentByte = "";
105
+ bitIndex = 0;
106
+ byteIndex++;
107
+ }
108
+ }
109
+ return byteArray;
110
+ }
111
+ /**
112
+ * Decode the byte array into a status list.
113
+ * @param byteArray
114
+ * @param bitsPerStatus
115
+ * @returns
116
+ */
117
+ static decodeStatusList(byteArray, bitsPerStatus) {
118
+ const numBits = bitsPerStatus;
119
+ const totalStatuses = byteArray.length * 8 / numBits;
120
+ const statusList = new Array(totalStatuses);
121
+ let bitIndex = 0;
122
+ for (let i = 0; i < totalStatuses; i++) {
123
+ const byte = byteArray[Math.floor(i * numBits / 8)];
124
+ let byteString = byte.toString(2);
125
+ if (byteString.length < 8) {
126
+ byteString = "0".repeat(8 - byteString.length) + byteString;
127
+ }
128
+ const status = byteString.slice(bitIndex, bitIndex + numBits);
129
+ const group = Math.floor(i / (8 / numBits));
130
+ const indexInGroup = i % (8 / numBits);
131
+ const position = group * (8 / numBits) + (8 / numBits + -1 - indexInGroup);
132
+ statusList[position] = Number.parseInt(status, 2);
133
+ bitIndex = (bitIndex + numBits) % 8;
134
+ }
135
+ return statusList;
136
+ }
137
+ };
138
+
139
+ // src/status-list-jwt.ts
140
+ import base64Url2 from "base64url";
141
+ function decodeJwt(jwt) {
142
+ const parts = jwt.split(".");
143
+ return JSON.parse(base64Url2.decode(parts[1]));
144
+ }
145
+ function createHeaderAndPayload(list, payload, header) {
146
+ if (!payload.iss) {
147
+ throw new Error("iss field is required");
148
+ }
149
+ if (!payload.sub) {
150
+ throw new Error("sub field is required");
151
+ }
152
+ if (!payload.iat) {
153
+ throw new Error("iat field is required");
154
+ }
155
+ header.typ = "statuslist+jwt";
156
+ payload.status_list = {
157
+ bits: list.getBitsPerStatus(),
158
+ lst: list.compressStatusList()
159
+ };
160
+ return { header, payload };
161
+ }
162
+ function getListFromStatusListJWT(jwt) {
163
+ const payload = decodeJwt(jwt);
164
+ const statusList = payload.status_list;
165
+ return StatusList.decompressStatusList(statusList.lst, statusList.bits);
166
+ }
167
+ function getStatusListFromJWT(jwt) {
168
+ const payload = decodeJwt(jwt);
169
+ return payload.status.status_list;
170
+ }
171
+ export {
172
+ StatusList,
173
+ createHeaderAndPayload,
174
+ getListFromStatusListJWT,
175
+ getStatusListFromJWT
176
+ };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@sd-jwt/jwt-status-list",
3
+ "version": "0.6.2-next.27+5711484",
4
+ "description": "Implementation based on https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "rm -rf **/dist && tsup",
16
+ "lint": "biome lint ./src",
17
+ "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov",
18
+ "test:node": "vitest run ./src/test/*.spec.ts",
19
+ "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom",
20
+ "test:cov": "vitest run --coverage"
21
+ },
22
+ "keywords": [
23
+ "sd-jwt-vc",
24
+ "status-list",
25
+ "sd-jwt"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js"
33
+ },
34
+ "author": "Mirko Mollik <mirkomollik@gmail.com>",
35
+ "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki",
36
+ "bugs": {
37
+ "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues"
38
+ },
39
+ "license": "Apache-2.0",
40
+ "devDependencies": {
41
+ "@types/pako": "^2.0.3",
42
+ "jose": "^5.2.2"
43
+ },
44
+ "dependencies": {
45
+ "@sd-jwt/types": "0.6.2-next.27+5711484",
46
+ "base64url": "^3.0.1",
47
+ "pako": "^2.1.0"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "tsup": {
53
+ "entry": [
54
+ "./src/index.ts"
55
+ ],
56
+ "sourceMap": true,
57
+ "splitting": false,
58
+ "clean": true,
59
+ "dts": true,
60
+ "format": [
61
+ "cjs",
62
+ "esm"
63
+ ]
64
+ },
65
+ "gitHead": "5711484f7aedc207763835f1f7f656a75558798d"
66
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './status-list.js';
2
+ export * from './status-list-jwt.js';
3
+ export * from './types.js';
@@ -0,0 +1,73 @@
1
+ import type { JwtPayload } from '@sd-jwt/types';
2
+ import { StatusList } from './status-list.js';
3
+ import type {
4
+ JWTwithStatusListPayload,
5
+ StatusListJWTHeaderParameters,
6
+ StatusListEntry,
7
+ StatusListJWTPayload,
8
+ } from './types.js';
9
+ import base64Url from 'base64url';
10
+
11
+ /**
12
+ * Decode a JWT and return the payload.
13
+ * @param jwt JWT token in compact JWS serialization.
14
+ * @returns Payload of the JWT.
15
+ */
16
+ function decodeJwt<T>(jwt: string): T {
17
+ const parts = jwt.split('.');
18
+ return JSON.parse(base64Url.decode(parts[1]));
19
+ }
20
+
21
+ /**
22
+ * Adds the status list to the payload and header of a JWT.
23
+ * @param list
24
+ * @param payload
25
+ * @param header
26
+ * @returns The header and payload with the status list added.
27
+ */
28
+ export function createHeaderAndPayload(
29
+ list: StatusList,
30
+ payload: JwtPayload,
31
+ header: StatusListJWTHeaderParameters,
32
+ ) {
33
+ // validate if the required fieds are present based on https://www.ietf.org/archive/id/draft-ietf-oauth-status-list-02.html#section-5.1
34
+
35
+ if (!payload.iss) {
36
+ throw new Error('iss field is required');
37
+ }
38
+ if (!payload.sub) {
39
+ throw new Error('sub field is required');
40
+ }
41
+ if (!payload.iat) {
42
+ throw new Error('iat field is required');
43
+ }
44
+ //exp and tll are optional. We will not validate the business logic of the values like exp > iat etc.
45
+
46
+ header.typ = 'statuslist+jwt';
47
+ payload.status_list = {
48
+ bits: list.getBitsPerStatus(),
49
+ lst: list.compressStatusList(),
50
+ };
51
+ return { header, payload };
52
+ }
53
+
54
+ /**
55
+ * Get the status list from a JWT, but do not verify the signature.
56
+ * @param jwt
57
+ * @returns
58
+ */
59
+ export function getListFromStatusListJWT(jwt: string): StatusList {
60
+ const payload = decodeJwt<StatusListJWTPayload>(jwt);
61
+ const statusList = payload.status_list;
62
+ return StatusList.decompressStatusList(statusList.lst, statusList.bits);
63
+ }
64
+
65
+ /**
66
+ * Get the status list entry from a JWT, but do not verify the signature.
67
+ * @param jwt
68
+ * @returns
69
+ */
70
+ export function getStatusListFromJWT(jwt: string): StatusListEntry {
71
+ const payload = decodeJwt<JWTwithStatusListPayload>(jwt);
72
+ return payload.status.status_list;
73
+ }
@@ -0,0 +1,167 @@
1
+ import { deflate, inflate } from 'pako';
2
+ import base64Url from 'base64url';
3
+ import type { BitsPerStatus } from './types.js';
4
+ /**
5
+ * StatusListManager is a class that manages a list of statuses with variable bit size.
6
+ */
7
+ export class StatusList {
8
+ private statusList: number[];
9
+ private bitsPerStatus: BitsPerStatus;
10
+ private totalStatuses: number;
11
+
12
+ /**
13
+ * Create a new StatusListManager instance.
14
+ * @param statusList
15
+ * @param bitsPerStatus
16
+ */
17
+ constructor(statusList: number[], bitsPerStatus: BitsPerStatus) {
18
+ if (![1, 2, 4, 8].includes(bitsPerStatus)) {
19
+ throw new Error('bitsPerStatus must be 1, 2, 4, or 8');
20
+ }
21
+ //check that the entries in the statusList are within the range of the bitsPerStatus
22
+ for (let i = 0; i < statusList.length; i++) {
23
+ if (statusList[i] > 2 ** bitsPerStatus) {
24
+ throw Error(
25
+ `Status value out of range at index ${i} with value ${statusList[i]}`,
26
+ );
27
+ }
28
+ }
29
+ this.statusList = statusList;
30
+ this.bitsPerStatus = bitsPerStatus;
31
+ this.totalStatuses = statusList.length;
32
+ }
33
+
34
+ /**
35
+ * Get the number of statuses.
36
+ * @returns
37
+ */
38
+ getBitsPerStatus(): BitsPerStatus {
39
+ return this.bitsPerStatus;
40
+ }
41
+
42
+ /**
43
+ * Get the status at a specific index.
44
+ * @param index
45
+ * @returns
46
+ */
47
+ getStatus(index: number): number {
48
+ if (index < 0 || index >= this.totalStatuses) {
49
+ throw new Error('Index out of bounds');
50
+ }
51
+ return this.statusList[index];
52
+ }
53
+
54
+ /**
55
+ * Set the status at a specific index.
56
+ * @param index
57
+ * @param value
58
+ */
59
+ setStatus(index: number, value: number): void {
60
+ if (index < 0 || index >= this.totalStatuses) {
61
+ throw new Error('Index out of bounds');
62
+ }
63
+ this.statusList[index] = value;
64
+ }
65
+
66
+ /**
67
+ * Compress the status list.
68
+ * @returns
69
+ */
70
+ compressStatusList(): string {
71
+ const byteArray = this.encodeStatusList();
72
+ const compressed = deflate(byteArray, { level: 9 });
73
+ return base64Url.encode(compressed as Buffer);
74
+ }
75
+
76
+ /**
77
+ * Decompress the compressed status list and return a new StatusList instance.
78
+ * @param compressed
79
+ * @param bitsPerStatus
80
+ * @returns
81
+ */
82
+ static decompressStatusList(
83
+ compressed: string,
84
+ bitsPerStatus: BitsPerStatus,
85
+ ): StatusList {
86
+ const decoded = new Uint8Array(
87
+ base64Url
88
+ .decode(compressed, 'binary')
89
+ .split('')
90
+ .map((c) => c.charCodeAt(0)),
91
+ );
92
+ try {
93
+ const decompressed = inflate(decoded);
94
+ const statusList = StatusList.decodeStatusList(
95
+ decompressed,
96
+ bitsPerStatus,
97
+ );
98
+ return new StatusList(statusList, bitsPerStatus);
99
+ } catch (err: unknown) {
100
+ throw new Error(`Decompression failed: ${err}`);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Encode the status list into a byte array.
106
+ * @returns
107
+ **/
108
+ public encodeStatusList(): Uint8Array {
109
+ const numBits = this.bitsPerStatus;
110
+ const numBytes = Math.ceil((this.totalStatuses * numBits) / 8);
111
+ const byteArray = new Uint8Array(numBytes);
112
+ let byteIndex = 0;
113
+ let bitIndex = 0;
114
+ let currentByte = '';
115
+ for (let i = 0; i < this.totalStatuses; i++) {
116
+ const status = this.statusList[i];
117
+ // Place bits from status into currentByte, starting from the most significant bit.
118
+ currentByte = status.toString(2).padStart(numBits, '0') + currentByte;
119
+ bitIndex += numBits;
120
+
121
+ // If currentByte is full or this is the last status, add it to byteArray and reset currentByte and bitIndex.
122
+ if (bitIndex >= 8 || i === this.totalStatuses - 1) {
123
+ // If this is the last status and bitIndex is not a multiple of 8, shift currentByte to the left.
124
+ if (i === this.totalStatuses - 1 && bitIndex % 8 !== 0) {
125
+ currentByte = currentByte.padStart(8, '0');
126
+ }
127
+ byteArray[byteIndex] = Number.parseInt(currentByte, 2);
128
+ currentByte = '';
129
+ bitIndex = 0;
130
+ byteIndex++;
131
+ }
132
+ }
133
+
134
+ return byteArray;
135
+ }
136
+
137
+ /**
138
+ * Decode the byte array into a status list.
139
+ * @param byteArray
140
+ * @param bitsPerStatus
141
+ * @returns
142
+ */
143
+ private static decodeStatusList(
144
+ byteArray: Uint8Array,
145
+ bitsPerStatus: BitsPerStatus,
146
+ ): number[] {
147
+ const numBits = bitsPerStatus;
148
+ const totalStatuses = (byteArray.length * 8) / numBits;
149
+ const statusList = new Array<number>(totalStatuses);
150
+ let bitIndex = 0; // Current position in byte
151
+ for (let i = 0; i < totalStatuses; i++) {
152
+ const byte = byteArray[Math.floor((i * numBits) / 8)];
153
+ let byteString = byte.toString(2);
154
+ if (byteString.length < 8) {
155
+ byteString = '0'.repeat(8 - byteString.length) + byteString;
156
+ }
157
+ const status = byteString.slice(bitIndex, bitIndex + numBits);
158
+ const group = Math.floor(i / (8 / numBits));
159
+ const indexInGroup = i % (8 / numBits);
160
+ const position =
161
+ group * (8 / numBits) + (8 / numBits + -1 - indexInGroup);
162
+ statusList[position] = Number.parseInt(status, 2);
163
+ bitIndex = (bitIndex + numBits) % 8;
164
+ }
165
+ return statusList;
166
+ }
167
+ }