UNPKG

hamok

Version:

Lightweight Distributed Object Storage on RAFT consensus algorithm

284 lines (239 loc) 9.75 kB
import { createStrToUint8ArrayCodec, HamokCodec, HamokDecoder, HamokEncoder } from '../common/HamokCodec'; import { createLogger } from '../common/logger'; import { StorageSyncRequest, StorageSyncResponse } from './messagetypes/StorageSync'; import { SubmitMessageRequest, SubmitMessageResponse } from './messagetypes/SubmitMessage'; import { HamokMessage, HamokMessage_MessageProtocol as HamokMessageProtocol, HamokMessage_MessageType as MessageType } from './HamokMessage'; import { OngoingRequestsNotification } from './messagetypes/OngoingRequests'; import * as Collections from '../common/Collections'; import { EndpointStatesNotification } from './messagetypes/EndpointNotification'; import { HelloNotification } from './messagetypes/HelloNotification'; import { JoinNotification } from './messagetypes/JoinNotification'; const logger = createLogger('GridCodec'); type Input = HelloNotification | JoinNotification | EndpointStatesNotification | OngoingRequestsNotification | StorageSyncRequest | StorageSyncResponse | SubmitMessageRequest | SubmitMessageResponse ; const EMPTY_ARRAY: Uint8Array[] = []; const strCodec = createStrToUint8ArrayCodec(); function setToArray<T>(set?: ReadonlySet<T>): T[] | undefined { if (!set) return; return Array.from(set); } function arrayToSet<T>(array?: T[]): Set<T> | undefined { if (!array) return; return new Set<T>(array); } export class HamokGridCodec implements HamokCodec<Input, HamokMessage> { public encode(input: Input): HamokMessage { switch (input.constructor) { case HelloNotification: return this.encodeHelloNotification(input as HelloNotification); case JoinNotification: return this.encodeJoinNotification(input as JoinNotification); case EndpointStatesNotification: return this.encodeEndpointStateNotification(input as EndpointStatesNotification); case OngoingRequestsNotification: return this.encodeOngoingRequestsNotification(input as OngoingRequestsNotification); case SubmitMessageRequest: return this.encodeSubmitMessageRequest(input as SubmitMessageRequest); case SubmitMessageResponse: return this.encodeSubmitMessageResponse(input as SubmitMessageResponse); default: throw new Error(`Cannot encode input${ input}`); } } public decode(message: HamokMessage): Input { switch (message.type) { case MessageType.HELLO_NOTIFICATION: return this.decodeHelloNotification(message); case MessageType.JOIN_NOTIFICATION: return this.decodeJoinNotification(message); case MessageType.ENDPOINT_STATES_NOTIFICATION: return this.decodeEndpointStateNotification(message); case MessageType.ONGOING_REQUESTS_NOTIFICATION: return this.decodeOngoingRequestsNotification(message); case MessageType.SUBMIT_MESSAGE_REQUEST: return this.decodeSubmitMessageRequest(message); case MessageType.SUBMIT_MESSAGE_RESPONSE: return this.decodeSubmitMessageResponse(message); default: throw new Error(`Cannot decode message${ message}`); } } public encodeHelloNotification(notification: HelloNotification): HamokMessage { logger.trace('encodeHelloNotification(): notification.customData %o', notification); return new HamokMessage({ // eslint-disable-next-line camelcase protocol: HamokMessageProtocol.GRID_COMMUNICATION_PROTOCOL, type: MessageType.HELLO_NOTIFICATION, sourceId: notification.sourcePeerId, destinationId: notification.destinationPeerId, raftLeaderId: notification.raftLeaderId, keys: notification.customData ? [ strCodec.encode(notification.customData) ] : [], requestId: notification.requestId, }); } public decodeHelloNotification(message: HamokMessage): HelloNotification { if (message.type !== MessageType.HELLO_NOTIFICATION) { throw new Error('decodeHelloNotification(): Message type must be HELLO_NOTIFICATION'); } const customData = message.keys.length === 1 ? strCodec.decode(message.keys[0]) : undefined; return new HelloNotification( message.sourceId!, message.destinationId, message.raftLeaderId, customData, message.requestId, ); } public encodeJoinNotification(notification: JoinNotification): HamokMessage { return new HamokMessage({ // eslint-disable-next-line camelcase protocol: HamokMessageProtocol.GRID_COMMUNICATION_PROTOCOL, type: MessageType.JOIN_NOTIFICATION, sourceId: notification.sourcePeerId, destinationId: notification.destinationPeerId, }); } public decodeJoinNotification(message: HamokMessage): JoinNotification { if (message.type !== MessageType.JOIN_NOTIFICATION) { throw new Error('decodeJoinNotification(): Message type must be JOIN_NOTIFICATION'); } return new JoinNotification( message.sourceId!, message.destinationId, ); } public encodeEndpointStateNotification(notification: EndpointStatesNotification): HamokMessage { const activeEndpointIds = setToArray<string>(notification.activeEndpointIds); const snapshot = notification.snapshot ? strCodec.encode(notification.snapshot) : undefined; logger.trace('encodeEndpointStateNotification(): activeEndpointIds %s', activeEndpointIds); return new HamokMessage({ // eslint-disable-next-line camelcase protocol: HamokMessageProtocol.GRID_COMMUNICATION_PROTOCOL, type: MessageType.ENDPOINT_STATES_NOTIFICATION, sourceId: notification.sourceEndpointId, destinationId: notification.destinationEndpointId, raftTerm: notification.term, raftCommitIndex: notification.commitIndex, raftLeaderNextIndex: notification.leaderNextIndex, raftNumberOfLogs: notification.numberOfLogs, activeEndpointIds, snapshot, requestId: notification.requestId, }); } public decodeEndpointStateNotification(message: HamokMessage): EndpointStatesNotification { if (message.type !== MessageType.ENDPOINT_STATES_NOTIFICATION) { throw new Error('decodeEndpointStateNotification(): Message type must be ENDPOINT_STATES_NOTIFICATION'); } logger.trace('decodeEndpointStateNotification(): activeEndpointIds %s', message.activeEndpointIds); const activeEndpointIds = arrayToSet<string>(message.activeEndpointIds); const snapshot = message.snapshot ? strCodec.decode(message.snapshot) : undefined; return new EndpointStatesNotification( message.sourceId!, message.destinationId!, message.raftTerm!, message.raftCommitIndex!, message.raftLeaderNextIndex!, message.raftNumberOfLogs!, activeEndpointIds, snapshot, message.requestId, ); } public encodeOngoingRequestsNotification(notification: OngoingRequestsNotification): HamokMessage { const keys = HamokGridCodec._encodeSet<string>(notification.requestIds, strCodec); return new HamokMessage({ type: MessageType.ONGOING_REQUESTS_NOTIFICATION, destinationId: notification.destinationEndpointId, keys, }); } public decodeOngoingRequestsNotification(message: HamokMessage): OngoingRequestsNotification { if (message.type !== MessageType.ONGOING_REQUESTS_NOTIFICATION) { throw new Error('decodeOngoingRequestsNotification(): Message type must be ONGOING_REQUESTS_NOTIFICATION'); } const requestIds = HamokGridCodec._decodeSet<string>(message.keys, strCodec); return new OngoingRequestsNotification( requestIds, message.destinationId, ); } public encodeSubmitMessageRequest(request: SubmitMessageRequest): HamokMessage { return new HamokMessage({ type: MessageType.SUBMIT_MESSAGE_REQUEST, requestId: request.requestId, embeddedMessages: [ request.entry! ], sourceId: request.sourceEndpointId, destinationId: request.destinationEndpointId, }); } public decodeSubmitMessageRequest(message: HamokMessage): SubmitMessageRequest { if (message.type !== MessageType.SUBMIT_MESSAGE_REQUEST) { throw new Error('decodeSubmitMessageRequest(): Message type must be SUBMIT_MESSAGE_REQUEST'); } let entry: HamokMessage | undefined; if (message.embeddedMessages && 0 < message.embeddedMessages.length) { entry = message.embeddedMessages[0]; if (1 < message.embeddedMessages.length) { logger.warn('decodeSubmitMessageRequest(): More than one message received for SubmitMessageRequest. Only the first one will be processed'); } } return new SubmitMessageRequest( message.requestId!, message.sourceId!, entry!, message.destinationId, ); } public encodeSubmitMessageResponse(response: SubmitMessageResponse): HamokMessage { return new HamokMessage({ type: MessageType.SUBMIT_MESSAGE_RESPONSE, requestId: response.requestId, success: response.success, destinationId: response.destinationEndpointId, raftLeaderId: response.leaderId, }); } public decodeSubmitMessageResponse(message: HamokMessage): SubmitMessageResponse { if (message.type !== MessageType.SUBMIT_MESSAGE_RESPONSE) { throw new Error('decodeSubmitMessageResponse(): Message type must be SUBMIT_MESSAGE_RESPONSE'); } return new SubmitMessageResponse( message.requestId!, Boolean(message.success), message.destinationId!, message.raftLeaderId!, ); } private static _encodeSet<T>(keys: ReadonlySet<T>, encoder: HamokEncoder<T, Uint8Array>): Uint8Array[] { if (keys.size < 1) { return EMPTY_ARRAY; } const result: Uint8Array[] = []; for (const key of keys) { const encodedKey = encoder.encode(key); result.push(encodedKey); } return result; } private static _decodeSet<T>(keys: Uint8Array[], decoder: HamokDecoder<T, Uint8Array>): ReadonlySet<T> { if (keys.length < 1) { return Collections.EMPTY_SET; } const result = new Set<T>(); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const decodedKey = decoder.decode(key); result.add(decodedKey); } return result; } }