blob: 709f03b7b57560792aa958811e32c318acb176dc [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import { uint8ToBuf, uint32ToBuf } from '../number.utils.js';
/**
* Global permissions for server-wide operations.
*/
export type GlobalPermissions = {
/** Can manage server configuration */
ManageServers: boolean,
/** Can read server information */
ReadServers: boolean,
/** Can manage users */
ManageUsers: boolean,
/** Can read user information */
ReadUsers: boolean,
/** Can manage streams */
ManageStreams: boolean,
/** Can read stream information */
ReadStreams: boolean,
/** Can manage topics */
ManageTopics: boolean,
/** Can read topic information */
ReadTopics: boolean,
/** Can poll messages */
PollMessages: boolean,
/** Can send messages */
SendMessages: boolean
};
/**
* Result of deserializing global permissions.
*/
type GlobalPermissionsDeserialized = {
/** Number of bytes consumed */
bytesRead: number,
/** Deserialized permissions */
data: GlobalPermissions
};
/**
* Permissions for a specific topic.
*/
export type TopicPermissions = {
/** Can manage the topic */
manage: boolean,
/** Can read the topic */
read: boolean,
/** Can poll messages from the topic */
pollMessages: boolean,
/** Can send messages to the topic */
sendMessages: boolean
};
/**
* Topic permissions with topic identifier.
*/
export type TopicPerms = {
/** Topic ID */
topicId: number,
/** Topic permissions */
permissions: TopicPermissions
};
/** Result of deserializing topic permissions */
type TopicPermissionsDeserialized = { bytesRead: number } & TopicPerms;
/**
* Permissions for a specific stream.
*/
export type StreamPermissions = {
/** Can manage the stream */
manageStream: boolean,
/** Can read the stream */
readStream: boolean,
/** Can manage topics within the stream */
manageTopics: boolean,
/** Can read topics within the stream */
readTopics: boolean,
/** Can poll messages from the stream */
pollMessages: boolean,
/** Can send messages to the stream */
sendMessages: boolean,
};
/**
* Stream permissions with stream identifier and topic permissions.
*/
export type StreamPerms = {
/** Stream ID */
streamId: number,
/** Stream-level permissions */
permissions: StreamPermissions,
/** Topic-specific permissions within the stream */
topics: TopicPerms[]
};
/** Result of deserializing stream permissions */
type StreamPermissionsDeserialized = { bytesRead: number } & StreamPerms;
/**
* Complete user permissions including global and stream-level permissions.
*/
export type UserPermissions = {
/** Global permissions */
global: GlobalPermissions,
/** Stream-specific permissions */
streams: StreamPerms[]
};
/**
* Converts a number to a boolean (1 = true).
*
* @param u - Number to convert
* @returns True if u equals 1
*/
const toBool = (u: number) => u === 1;
/**
* Converts a boolean to a byte value.
*
* @param b - Boolean value
* @returns 1 if true, 0 if false
*/
const boolToByte = (b: boolean) => b ? 1 : 0;
/**
* Deserializes global permissions from a buffer.
*
* @param p - Buffer containing serialized permissions
* @param pos - Starting position in the buffer
* @returns Object with bytes read and deserialized permissions
*/
export const deserializeGlobalPermissions =
(p: Buffer, pos = 0): GlobalPermissionsDeserialized => {
return {
bytesRead: 10,
data: {
ManageServers: toBool(p.readUInt8(pos)),
ReadServers: toBool(p.readUInt8(pos + 1)),
ManageUsers: toBool(p.readUInt8(pos + 2)),
ReadUsers: toBool(p.readUInt8(pos + 3)),
ManageStreams: toBool(p.readUInt8(pos + 4)),
ReadStreams: toBool(p.readUInt8(pos + 5)),
ManageTopics: toBool(p.readUInt8(pos + 6)),
ReadTopics: toBool(p.readUInt8(pos + 7)),
PollMessages: toBool(p.readUInt8(pos + 8)),
SendMessages: toBool(p.readUInt8(pos + 9)),
}
};
};
/**
* Serializes global permissions to a buffer.
*
* @param p - Global permissions to serialize
* @returns Serialized permissions buffer (10 bytes)
*/
export const serializeGlobalPermission = (p: GlobalPermissions) => {
return Buffer.concat([
uint8ToBuf(boolToByte(p.ManageServers)),
uint8ToBuf(boolToByte(p.ReadServers)),
uint8ToBuf(boolToByte(p.ManageUsers)),
uint8ToBuf(boolToByte(p.ReadUsers)),
uint8ToBuf(boolToByte(p.ManageStreams)),
uint8ToBuf(boolToByte(p.ReadStreams)),
uint8ToBuf(boolToByte(p.ManageTopics)),
uint8ToBuf(boolToByte(p.ReadTopics)),
uint8ToBuf(boolToByte(p.PollMessages)),
uint8ToBuf(boolToByte(p.SendMessages)),
]);
}
/**
* Deserializes topic permissions from a buffer.
*
* @param p - Buffer containing serialized permissions
* @param pos - Starting position in the buffer
* @returns Object with bytes read, topic ID, and permissions
*/
export const deserializeTopicPermissions =
(p: Buffer, pos = 0): TopicPermissionsDeserialized => {
const topicId = p.readUInt32LE(pos);
const permissions = {
manage: toBool(p.readUInt8(pos + 4)),
read: toBool(p.readUInt8(pos + 5)),
pollMessages: toBool(p.readUInt8(pos + 6)),
sendMessages: toBool(p.readUInt8(pos + 7)),
};
return {
bytesRead: 8,
topicId,
permissions
}
};
/**
* Serializes topic permissions to a buffer.
*
* @param p - Topic permissions to serialize
* @returns Serialized permissions buffer (8 bytes)
*/
export const serializeTopicPermissions = (p: TopicPerms) => {
return Buffer.concat([
uint32ToBuf(p.topicId),
uint8ToBuf(boolToByte(p.permissions.manage)),
uint8ToBuf(boolToByte(p.permissions.read)),
uint8ToBuf(boolToByte(p.permissions.pollMessages)),
uint8ToBuf(boolToByte(p.permissions.sendMessages)),
]);
};
/**
* Deserializes stream permissions from a buffer.
* Includes nested topic permissions if present.
*
* @param p - Buffer containing serialized permissions
* @param pos - Starting position in the buffer
* @returns Object with bytes read, stream ID, permissions, and topics
*/
export const deserializeStreamPermissions =
(p: Buffer, pos = 0): StreamPermissionsDeserialized => {
const start = pos;
const streamId = p.readUInt32LE(pos);
const permissions = {
manageStream: toBool(p.readUInt8(pos + 4)),
readStream: toBool(p.readUInt8(pos + 5)),
manageTopics: toBool(p.readUInt8(pos + 6)),
readTopics: toBool(p.readUInt8(pos + 7)),
pollMessages: toBool(p.readUInt8(pos + 8)),
sendMessages: toBool(p.readUInt8(pos + 9)),
}
pos += 10;
const topics: TopicPerms[] = [];
const hasTopics = toBool(p.readUInt8(pos));
pos += 1;
if (hasTopics) {
let hasNextTopic = true;
while (hasNextTopic) {
const { bytesRead, topicId, permissions } = deserializeTopicPermissions(p, pos);
pos += bytesRead;
topics.push({ topicId, permissions });
hasNextTopic = toBool(p.readUInt8(pos));
pos += 1;
}
}
return {
bytesRead: pos - start,
streamId,
permissions,
topics
}
};
/**
* Serializes stream permissions to a buffer.
* Includes nested topic permissions if present.
*
* @param p - Stream permissions to serialize
* @returns Serialized permissions buffer
*/
export const serializeStreamPermissions = (p: StreamPerms) => {
const bStream = Buffer.concat([
uint32ToBuf(p.streamId),
uint8ToBuf(boolToByte(p.permissions.manageStream)),
uint8ToBuf(boolToByte(p.permissions.readStream)),
uint8ToBuf(boolToByte(p.permissions.manageTopics)),
uint8ToBuf(boolToByte(p.permissions.readTopics)),
uint8ToBuf(boolToByte(p.permissions.pollMessages)),
uint8ToBuf(boolToByte(p.permissions.sendMessages)),
]);
const hasTopics = p.topics.length > 0;
const bHasTopics = uint8ToBuf(boolToByte(hasTopics));
const bHead = Buffer.concat([bStream, bHasTopics]);
if (!hasTopics)
return bHead;
const bTopics = p.topics.map((topic, index) => Buffer.concat([
serializeTopicPermissions(topic),
uint8ToBuf(boolToByte(index < p.topics.length - 1))
]));
return Buffer.concat([bHead, ...bTopics]);
};
/**
* Deserializes complete user permissions from a buffer.
* Includes global permissions and stream-level permissions.
*
* @param p - Buffer containing serialized permissions
* @param pos - Starting position in the buffer
* @returns Deserialized user permissions
*/
export const deserializePermissions = (p: Buffer, pos = 0): UserPermissions => {
const { bytesRead, data } = deserializeGlobalPermissions(p, pos);
pos += bytesRead;
const streams: StreamPerms[] = [];
const hasStreams = toBool(p.readUInt8(pos));
pos += 1;
if (hasStreams) {
let hasNextStream = true;
while (hasNextStream) {
const {
bytesRead, streamId, permissions, topics
} = deserializeStreamPermissions(p, pos);
streams.push({ streamId, permissions, topics });
pos += bytesRead;
hasNextStream = toBool(p.readUInt8(pos));
pos += 1;
}
}
return {
global: data,
streams
}
};
/**
* Serializes user permissions to a buffer.
*
* @param p - User permissions to serialize
* @returns Serialized permissions buffer
*/
export const serializePermissions = (p: UserPermissions) => {
const bGlobal = serializeGlobalPermission(p.global);
const hasStreams = p.streams.length > 0;
const bHasStreams = uint8ToBuf(boolToByte(hasStreams));
const bHead = Buffer.concat([bGlobal, bHasStreams]);
if (!hasStreams)
return bHead;
const bStreams = p.streams.map((stream, index) => Buffer.concat([
serializeStreamPermissions(stream),
uint8ToBuf(boolToByte(index < p.streams.length - 1))
]));
return Buffer.concat([bHead, ...bStreams]);
};