hamok
Version:
Lightweight Distributed Object Storage on RAFT consensus algorithm
284 lines (239 loc) • 9.75 kB
text/typescript
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;
}
}