Skip to content

Commit

Permalink
feat: add validation to other ws mesages
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-yarmosh committed Feb 3, 2025
1 parent 5f89719 commit 5c309ea
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 25 deletions.
8 changes: 4 additions & 4 deletions src/lib/ws/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ io

// Handlers
subscribeWithHandler(socket, 'probe:status:update', handleStatusUpdate(probe));
socket.on('probe:isIPv6Supported:update', handleIsIPv6SupportedUpdate(probe));
socket.on('probe:isIPv4Supported:update', handleIsIPv4SupportedUpdate(probe));
socket.on('probe:dns:update', handleDnsUpdate(probe));
socket.on('probe:stats:report', handleStatsReport(probe));
subscribeWithHandler(socket, 'probe:isIPv6Supported:update', handleIsIPv6SupportedUpdate(probe));
subscribeWithHandler(socket, 'probe:isIPv4Supported:update', handleIsIPv4SupportedUpdate(probe));
subscribeWithHandler(socket, 'probe:dns:update', handleDnsUpdate(probe));
subscribeWithHandler(socket, 'probe:stats:report', handleStatsReport(probe));
socket.onAnyOutgoing(listenMeasurementRequest(probe));
subscribeWithHandler(socket, 'probe:measurement:ack', handleMeasurementAck(probe));
subscribeWithHandler(socket, 'probe:measurement:progress', handleMeasurementProgress(probe));
Expand Down
22 changes: 16 additions & 6 deletions src/lib/ws/helper/subscribe-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ServerSocket } from '../server.js';
const logger = scopedLogger('ws:handler:error');
const isError = (error: unknown): error is Error => Boolean(error as Error['message']);

type HandlerMethod = (...args: never[]) => Promise<void>;
type HandlerMethod = (...args: never[]) => Promise<void> | void;

export const subscribeWithHandler = (socket: ServerSocket, event: string, method: HandlerMethod) => {
socket.on(event, async (...args) => {
Expand All @@ -21,11 +21,7 @@ export const subscribeWithHandler = (socket: ServerSocket, event: string, method
}

if (Joi.isError(error)) {
const messages = error.details.map(({ message, context }) => `${message}. Received: "${context?.value}"`);

if (messages.length) {
details = messages.join('\n');
}
details = formatJoiError(error);
}

logger.info(`Event "${event}" failed to handle for (${details})`, {
Expand All @@ -36,3 +32,17 @@ export const subscribeWithHandler = (socket: ServerSocket, event: string, method
}
});
};

const formatJoiError = (error: Joi.ValidationError) => {
const messages = error.details.map(({ message, context }) => {
let str = `${message}.`;

if (context?.value) {
str += `Received: "${context?.value}".`;
}

return str;
});

return messages.length ? error.message : messages.join('\n');
};
22 changes: 20 additions & 2 deletions src/measurement/handler/progress.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import Joi from 'joi';
import type { Probe } from '../../probe/types.js';
import type { MeasurementProgressMessage } from '../types.js';
import { getMeasurementRunner } from '../runner.js';
import { getProbeValidator } from '../../lib/probe-validator.js';

const schema = Joi.object<MeasurementProgressMessage>({
testId: Joi.string().required(),
measurementId: Joi.string().required(),
overwrite: Joi.boolean(),
result: Joi.object({
rawOutput: Joi.string().required(),
rawHeaders: Joi.string(),
rawBody: Joi.string(),
}).required(),
}).required();

const runner = getMeasurementRunner();

export const handleMeasurementProgress = (probe: Probe) => async (data: MeasurementProgressMessage): Promise<void> => {
await getProbeValidator().validateProbe(data.measurementId, data.testId, probe.uuid);
await runner.recordProgress(data);
const validation = schema.validate(data);

if (validation.error) {
throw validation.error;
}

await getProbeValidator().validateProbe(validation.value.measurementId, validation.value.testId, probe.uuid);
await runner.recordProgress(validation.value);
};
38 changes: 37 additions & 1 deletion src/measurement/handler/result.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,47 @@
import Joi from 'joi';
import type { Probe } from '../../probe/types.js';
import type { MeasurementResultMessage } from '../types.js';
import type { MeasurementResultMessage, PingResult } from '../types.js';
import { getMeasurementRunner } from '../runner.js';
import { getProbeValidator } from '../../lib/probe-validator.js';

const pingResultSchema = Joi.object<PingResult>({
status: Joi.string().required(),
rawOutput: Joi.string().required(),
resolvedAddress: Joi.string().required().allow(null),
resolvedHostname: Joi.string().required().allow(null),
timings: Joi.array().items(Joi.object({
rtt: Joi.number().required(),
ttl: Joi.number().required(),
})).required(),
stats: Joi.object({
min: Joi.number().required().allow(null),
max: Joi.number().required().allow(null),
avg: Joi.number().required().allow(null),
total: Joi.number().required().allow(null),
loss: Joi.number().required().allow(null),
rcv: Joi.number().required().allow(null),
drop: Joi.number().required().allow(null),
}).required(),
});

const schema = Joi.object<MeasurementResultMessage>({
testId: Joi.string().required(),
measurementId: Joi.string().required(),
overwrite: Joi.boolean(),
result: Joi.alternatives([
pingResultSchema,
]).required(),
}).required();

const runner = getMeasurementRunner();

export const handleMeasurementResult = (probe: Probe) => async (data: MeasurementResultMessage): Promise<void> => {
const validation = schema.validate(data);

if (validation.error) {
throw validation.error;
}

await getProbeValidator().validateProbe(data.measurementId, data.testId, probe.uuid);
await runner.recordResult(data);
};
14 changes: 9 additions & 5 deletions src/measurement/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ type PingTiming = {
};

export type PingResult = TestResult & {
resolvedAddress: string | null,
resolvedHostname: string | null,
timings: PingTiming[];
stats: {
min: number;
avg: number;
max: number;
stddev: number;
packetLoss: number;
min: number | null,
max: number | null,
avg: number | null,
total: number | null,
loss: number | null,
rcv: number | null,
drop: number | null,
};
};

Expand Down
11 changes: 10 additions & 1 deletion src/probe/handler/dns.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import Joi from 'joi';
import type { Probe } from '../types.js';

const schema = Joi.array<string[]>().items(Joi.string()).required();

export const handleDnsUpdate = (probe: Probe) => (list: string[]): void => {
probe.resolvers = list;
const validation = schema.validate(list);

if (validation.error) {
throw validation.error;
}

probe.resolvers = validation.value;
};
19 changes: 17 additions & 2 deletions src/probe/handler/ip-version.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import Joi from 'joi';
import type { Probe } from '../types.js';

const schema = Joi.boolean().required();

export const handleIsIPv4SupportedUpdate = (probe: Probe) => (isIPv4Supported: boolean): void => {
probe.isIPv4Supported = isIPv4Supported;
const validation = schema.validate(isIPv4Supported);

if (validation.error) {
throw validation.error;
}

probe.isIPv4Supported = validation.value;
};

export const handleIsIPv6SupportedUpdate = (probe: Probe) => (isIPv6Supported: boolean): void => {
probe.isIPv6Supported = isIPv6Supported;
const validation = schema.validate(isIPv6Supported);

if (validation.error) {
throw validation.error;
}

probe.isIPv6Supported = validation.value;
};
20 changes: 19 additions & 1 deletion src/probe/handler/stats.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
import Joi from 'joi';
import type { Probe, ProbeStats } from '../types.js';

const schema = Joi.object<ProbeStats>({
cpu: Joi.object({
load: Joi.array().items(Joi.object({
usage: Joi.number().required(),
})).required(),
}).required(),
jobs: Joi.object({
count: Joi.number().required(),
}).required(),
}).required();

export const handleStatsReport = (probe: Probe) => (report: ProbeStats): void => {
probe.stats = report;
const validation = schema.validate(report);

if (validation.error) {
throw validation.error;
}

probe.stats = validation.value;
};
6 changes: 3 additions & 3 deletions src/probe/handler/status.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Joi from 'joi';
import type { Probe } from '../types.js';

const schema = Joi.string<Probe['status']>().valid('initializing', 'ready', 'unbuffer-missing', 'ping-test-failed', 'sigterm');
const schema = Joi.string<Probe['status']>().valid('initializing', 'ready', 'unbuffer-missing', 'ping-test-failed', 'sigterm').required();

export const handleStatusUpdate = (probe: Probe) => async (input: unknown) => {
const validation = schema.validate(input);
export const handleStatusUpdate = (probe: Probe) => (status: Probe['status']) => {
const validation = schema.validate(status);

if (validation.error) {
throw validation.error;
Expand Down

0 comments on commit 5c309ea

Please sign in to comment.