Skip to content

Commit

Permalink
[lib] Introduce rolePermissionToBitmaskHex
Browse files Browse the repository at this point in the history
Summary:
Introduce `rolePermissionToBitmaskHex` which turns `threadRolePermission` string into hex-encoded bitmask.

Context: https://linear.app/comm/issue/ENG-5388/encodepermissions-for-rolepermissionsblob

Test Plan: Unit tests, more testing in upcoming diff which handles decoding back to `rolePermissionString`

Reviewers: ashoat, ginsu, tomek, rohan

Reviewed By: ashoat

Subscribers: wyilio

Differential Revision: https://phab.comm.dev/D9662
  • Loading branch information
atulsmadhugiri committed Nov 2, 2023
1 parent 94abca6 commit 4846f86
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 26 deletions.
104 changes: 78 additions & 26 deletions lib/permissions/minimally-encoded-thread-permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,59 @@

import invariant from 'invariant';

import { parseThreadPermissionString } from './prefixes.js';
import type {
ThreadPermission,
ThreadPermissionsInfo,
} from '../types/thread-permission-types.js';
import { entries } from '../utils/objects.js';

const minimallyEncodedThreadPermissions = Object.freeze({
// `baseRolePermissionEncoding` maps permission names to indices.
// These indices represent the 6-bit basePermission part of the 10-bit role
// permission encoding created by `rolePermissionToBitmaskHex`.
// The 6-bit basePermission allows for up to 2^6 = 64 different permissions.
// If more than 64 permissions are needed, the encoding in
// `rolePermissionToBitmaskHex` will need to be updated to accommodate this.
const baseRolePermissionEncoding = Object.freeze({
// TODO (atul): Update flow to `194.0.0` for bigint support
// $FlowIssue bigint-unsupported
know_of: BigInt(1) << BigInt(0),
visible: BigInt(1) << BigInt(1),
voiced: BigInt(1) << BigInt(2),
edit_entries: BigInt(1) << BigInt(3),
edit_thread: BigInt(1) << BigInt(4), // EDIT_THREAD_NAME
edit_thread_description: BigInt(1) << BigInt(5),
edit_thread_color: BigInt(1) << BigInt(6),
delete_thread: BigInt(1) << BigInt(7),
create_subthreads: BigInt(1) << BigInt(8), // CREATE_SUBCHANNELS
create_sidebars: BigInt(1) << BigInt(9),
join_thread: BigInt(1) << BigInt(10),
edit_permissions: BigInt(1) << BigInt(11),
add_members: BigInt(1) << BigInt(12),
remove_members: BigInt(1) << BigInt(13),
change_role: BigInt(1) << BigInt(14),
leave_thread: BigInt(1) << BigInt(15),
react_to_message: BigInt(1) << BigInt(16),
edit_message: BigInt(1) << BigInt(17),
edit_thread_avatar: BigInt(1) << BigInt(18),
manage_pins: BigInt(1) << BigInt(19),
manage_invite_links: BigInt(1) << BigInt(20),
know_of: BigInt(0),
visible: BigInt(1),
voiced: BigInt(2),
edit_entries: BigInt(3),
edit_thread: BigInt(4), // EDIT_THREAD_NAME
edit_thread_description: BigInt(5),
edit_thread_color: BigInt(6),
delete_thread: BigInt(7),
create_subthreads: BigInt(8), // CREATE_SUBCHANNELS
create_sidebars: BigInt(9),
join_thread: BigInt(10),
edit_permissions: BigInt(11),
add_members: BigInt(12),
remove_members: BigInt(13),
change_role: BigInt(14),
leave_thread: BigInt(15),
react_to_message: BigInt(16),
edit_message: BigInt(17),
edit_thread_avatar: BigInt(18),
manage_pins: BigInt(19),
manage_invite_links: BigInt(20),
});

// `minimallyEncodedThreadPermissions` is used to map each permission
// to its respective bitmask where the index from `baseRolePermissionEncoding`
// is used to set a specific bit in the bitmask. This is used in the
// `permissionsToBitmaskHex` function where each permission is represented as a
// single bit and the final bitmask is the union of all granted permissions.
const minimallyEncodedThreadPermissions = Object.fromEntries(
Object.keys(baseRolePermissionEncoding).map((key, idx) => [
key,
BigInt(1) << BigInt(idx),
]),
);

// This function converts a set of permissions to a hex-encoded bitmask.
// Each permission is represented as a single bit in the bitmask.
const permissionsToBitmaskHex = (
permissions: ThreadPermissionsInfo,
): string => {
Expand Down Expand Up @@ -70,8 +91,39 @@ const hasPermission = (
return (permissionsBitmask & permissionBitmask) !== BigInt(0);
};

export {
minimallyEncodedThreadPermissions,
permissionsToBitmaskHex,
hasPermission,
const propagationPrefixes = Object.freeze({
'': BigInt(0),
'descendant_': BigInt(1),
'child_': BigInt(2),
});
const filterPrefixes = Object.freeze({
'': BigInt(0),
'open_': BigInt(1),
'toplevel_': BigInt(2),
'opentoplevel_': BigInt(3),
});

// Role Permission Bitmask Structure
// [9 8 7 6 5 4 3 2 1 0] - bit positions
// [b b b b b b p p f f] - symbol representation
// b = basePermission (6 bits)
// p = propagationPrefix (2 bits)
// f = filterPrefix (2 bits)
const rolePermissionToBitmaskHex = (threadRolePermission: string): string => {
const parsed = parseThreadPermissionString(threadRolePermission);
const basePermissionBits =
baseRolePermissionEncoding[parsed.permission] & BigInt(63);
const propagationPrefixBits =
propagationPrefixes[parsed.propagationPrefix ?? ''] & BigInt(3);
const filterPrefixBits =
filterPrefixes[parsed.filterPrefix ?? ''] & BigInt(3);

const bitmask =
(basePermissionBits << BigInt(4)) |
(propagationPrefixBits << BigInt(2)) |
filterPrefixBits;

return bitmask.toString(16).padStart(3, '0');
};

export { permissionsToBitmaskHex, hasPermission, rolePermissionToBitmaskHex };
37 changes: 37 additions & 0 deletions lib/permissions/minimally-encoded-thread-permissions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import {
hasPermission,
permissionsToBitmaskHex,
rolePermissionToBitmaskHex,
} from './minimally-encoded-thread-permissions.js';

describe('minimallyEncodedThreadPermissions', () => {
Expand Down Expand Up @@ -51,3 +52,39 @@ describe('minimallyEncodedThreadPermissions', () => {
expect(hasPermission(permissionsBitmask, 'edit_message')).toBe(true);
});
});

describe('rolePermissionToBitmaskHex', () => {
it('should encode `child_opentoplevel_visible` successfully', () => {
expect(rolePermissionToBitmaskHex('child_opentoplevel_visible')).toBe(
'01b',
);
});

it('should encode `child_opentoplevel_know_of` successfully', () => {
expect(rolePermissionToBitmaskHex('child_opentoplevel_know_of')).toBe(
'00b',
);
});

it('should encode `child_toplevel_visible` successfully', () => {
expect(rolePermissionToBitmaskHex('child_toplevel_visible')).toBe('01a');
});

it('should encode `child_toplevel_know_of` successfully', () => {
expect(rolePermissionToBitmaskHex('child_toplevel_know_of')).toBe('00a');
});

it('should encode `child_opentoplevel_join_thread` successfully', () => {
expect(rolePermissionToBitmaskHex('child_opentoplevel_join_thread')).toBe(
'0ab',
);
});

it('should encode `child_visible` successfully', () => {
expect(rolePermissionToBitmaskHex('child_visible')).toBe('018');
});

it('should encode `child_know_of` successfully', () => {
expect(rolePermissionToBitmaskHex('child_know_of')).toBe('008');
});
});

0 comments on commit 4846f86

Please sign in to comment.