diff --git a/lib/permissions/minimally-encoded-thread-permissions.js b/lib/permissions/minimally-encoded-thread-permissions.js --- a/lib/permissions/minimally-encoded-thread-permissions.js +++ b/lib/permissions/minimally-encoded-thread-permissions.js @@ -112,9 +112,50 @@ return bitmask.toString(16).padStart(3, '0'); }; +const inverseBaseRolePermissionEncoding = new Map( + Object.entries(baseRolePermissionEncoding).map(([key, value]) => [ + value, + key, + ]), +); + +// $FlowIssue bigint-unsupported +const inversePropagationPrefixes: Map = new Map( + Object.entries(propagationPrefixes).map(([key, value]) => [value, key]), +); +// $FlowIssue bigint-unsupported +const inverseFilterPrefixes: Map = new Map( + Object.entries(filterPrefixes).map(([key, value]) => [value, key]), +); +const decodeRolePermissionBitmask = (bitmask: string): string => { + const bitmaskInt = BigInt(`0x${bitmask}`); + const basePermission = bitmaskInt >> BigInt(4); + const propagationPrefix = (bitmaskInt >> BigInt(2)) & BigInt(3); + const filterPrefix = bitmaskInt & BigInt(3); + + const basePermissionString = + inverseBaseRolePermissionEncoding.get(basePermission); + const propagationPrefixString = + inversePropagationPrefixes.get(propagationPrefix) ?? ''; + const filterPrefixString = inverseFilterPrefixes.get(filterPrefix) ?? ''; + + invariant( + basePermissionString !== null && + basePermissionString !== undefined && + propagationPrefixString !== null && + propagationPrefixString !== undefined && + filterPrefixString !== null && + filterPrefixString !== undefined, + 'invalid bitmask', + ); + + return `${propagationPrefixString}${filterPrefixString}${basePermissionString}`; +}; + export { minimallyEncodedThreadPermissions, permissionsToBitmaskHex, hasPermission, rolePermissionToBitmaskHex, + decodeRolePermissionBitmask, }; diff --git a/lib/permissions/minimally-encoded-thread-permissions.test.js b/lib/permissions/minimally-encoded-thread-permissions.test.js --- a/lib/permissions/minimally-encoded-thread-permissions.test.js +++ b/lib/permissions/minimally-encoded-thread-permissions.test.js @@ -1,6 +1,7 @@ // @flow import { + decodeRolePermissionBitmask, hasPermission, permissionsToBitmaskHex, rolePermissionToBitmaskHex, @@ -88,3 +89,39 @@ expect(rolePermissionToBitmaskHex('child_know_of')).toBe('008'); }); }); + +describe('decodeRolePermissionBitmask', () => { + it('should decode `01b` to `child_opentoplevel_visible` successfully', () => { + expect(decodeRolePermissionBitmask('01b')).toBe( + 'child_opentoplevel_visible', + ); + }); + + it('should decode `00b` to `child_opentoplevel_know_of` successfully', () => { + expect(decodeRolePermissionBitmask('00b')).toBe( + 'child_opentoplevel_know_of', + ); + }); + + it('should decode `01a` to `child_toplevel_visible` successfully', () => { + expect(decodeRolePermissionBitmask('01a')).toBe('child_toplevel_visible'); + }); + + it('should decode `00a` to `child_toplevel_know_of` successfully', () => { + expect(decodeRolePermissionBitmask('00a')).toBe('child_toplevel_know_of'); + }); + + it('should decode `0ab` to `child_opentoplevel_join_thread` successfully', () => { + expect(decodeRolePermissionBitmask('0ab')).toBe( + 'child_opentoplevel_join_thread', + ); + }); + + it('should decode `018` to `child_visible` successfully', () => { + expect(decodeRolePermissionBitmask('018')).toBe('child_visible'); + }); + + it('should decode `008` to `child_know_of` successfully', () => { + expect(decodeRolePermissionBitmask('008')).toBe('child_know_of'); + }); +});