Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aliases: only perform parseFloat if the value is a string #2542

Merged
merged 5 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions packages/common/src/lib/common/aliasProcessing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
interface ApplyAliasTransformerOptions {
/** State used for calculations */
state: ioBroker.State;
/** Properties from this StateCommon will be provided first to the conversion function */
firstCommon: Partial<ioBroker.StateCommon>;
/** Properties from this StateCommon will be provided second to the conversion function */
secondCommon: Partial<ioBroker.StateCommon>;
/** The actual transformer function as a string */
transformer: string;
/** If this is a read function, determines the naming of the passed variables */
isRead: boolean;
}

interface ApplyAliasConvenienceConversionOptions {
/** State used for calculations */
state: ioBroker.State;
/** The common attribute of the alias target */
targetCommon?: Partial<ioBroker.StateCommon>;
}

interface ApplyAliasAutoScalingOptions extends ApplyAliasConvenienceConversionOptions {
/** The common attribute of the alias source */
sourceCommon?: Partial<ioBroker.StateCommon>;
}

/**
* Applies a user-given transformer function and provides the type, min and max of the
* passed StateCommon variables as well as the state's value
*
* @param options state, common information and transformer function
*/
export function applyAliasTransformer(options: ApplyAliasTransformerOptions): ioBroker.StateValue {
const { state, firstCommon, secondCommon, transformer, isRead } = options;

const prefix = isRead ? 's' : 't';

const func = new Function(
'val',
'type',
'min',
'max',
`${prefix}Type`,
`${prefix}Min`,
`${prefix}Max`,
`return ${transformer}`

Check warning

Code scanning / CodeQL

Unsafe code constructed from library input Medium

This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
This string concatenation which depends on
library input
is later
interpreted as code
.
);

return func(
state.val,
firstCommon.type,
firstCommon.min,
firstCommon.max,
secondCommon.type,
secondCommon.min,
secondCommon.max
);
}

/**
* Applies some convenience conversions of aliases, e.g. transforming string 'off' to a boolean false, if target is a boolean
*
* @param options state and target common information
*/
export function applyAliasConvenienceConversion(options: ApplyAliasConvenienceConversionOptions): ioBroker.StateValue {
const { targetCommon, state } = options;

if (targetCommon && typeof state.val !== targetCommon.type && state.val !== null) {
if (targetCommon.type === 'boolean') {
const lowerVal = typeof state.val === 'string' ? state.val.toLowerCase() : state.val;
if (lowerVal === 'off' || lowerVal === 'aus' || state.val === '0') {
return false;
} else {
// this also handles strings like "EIN" or such that will be true
return !!state.val;
}
} else if (targetCommon.type === 'number' && typeof state.val === 'string') {
return parseFloat(state.val);
} else if (targetCommon.type === 'string') {
return state.val.toString();
}
}

return state.val;
}

/**
* Applies autoscaling between alias source and target if one has % unit and the other not
*
* @param options state, source and target common information
*/
export function applyAliasAutoScaling(options: ApplyAliasAutoScalingOptions): ioBroker.StateValue {
const { state, sourceCommon, targetCommon } = options;

// auto-scaling, only if val not null and unit for target (x)or source is %
if (
((targetCommon?.alias && !targetCommon.alias.read) || (sourceCommon?.alias && !sourceCommon.alias.write)) &&
state.val !== null
) {
if (
targetCommon &&
targetCommon.type === 'number' &&
targetCommon.unit === '%' &&
sourceCommon &&
sourceCommon.type === 'number' &&
sourceCommon.unit !== '%' &&
sourceCommon.min !== undefined &&
sourceCommon.max !== undefined
) {
// scale target between 0 and 100 % based on sources min/max
return (((state.val as number) - sourceCommon.min) / (sourceCommon.max - sourceCommon.min)) * 100;
} else if (
sourceCommon &&
sourceCommon.type === 'number' &&
sourceCommon.unit === '%' &&
targetCommon &&
targetCommon.unit !== '%' &&
targetCommon.type === 'number' &&
targetCommon.min !== undefined &&
targetCommon.max !== undefined
) {
// scale target based on its min/max by its source (assuming source is meant to be 0 - 100 %)
return ((targetCommon.max - targetCommon.min) * (state.val as number)) / 100 + targetCommon.min;
}
}

return state.val;
}
114 changes: 22 additions & 92 deletions packages/common/src/lib/common/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { maybeCallbackWithError } from './maybeCallback';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const extend = require('node.extend');
import { setDefaultResultOrder } from 'dns';
import { applyAliasAutoScaling, applyAliasConvenienceConversion, applyAliasTransformer } from './aliasProcessing';

type DockerInformation =
| {
Expand Down Expand Up @@ -2658,7 +2659,8 @@ export function measureEventLoopLag(ms: number, cb: (eventLoopLag?: number) => v
}

/**
* This function convert state values by read and write of aliases. Function is synchron.
* This function convert state values by read and write of aliases. Function is synchronous.
* On errors, null is returned instead
*
* @param options
*/
Expand All @@ -2682,118 +2684,46 @@ export function formatAliasValue(options: FormatAliasValueOptions): ioBroker.Sta
return null;
}
try {
// process the value here
const func = new Function(
'val',
'type',
'min',
'max',
'sType',
'sMin',
'sMax',
`return ${targetCommon.alias.read}`
);
state.val = func(
state.val,
targetCommon.type,
targetCommon.min,
targetCommon.max,
sourceCommon.type,
sourceCommon.min,
sourceCommon.max
);
state.val = applyAliasTransformer({
transformer: targetCommon.alias.read,
firstCommon: targetCommon,
secondCommon: sourceCommon,
isRead: true,
state
});
} catch (e) {
logger.error(
`${logNamespace} Invalid read function for "${targetId}": "${targetCommon.alias.read}" => ${e.message}`
`${logNamespace}Invalid read function for "${targetId}": "${targetCommon.alias.read}" => ${e.message}`
);
return null;
}
}

if (sourceCommon && sourceCommon.alias && sourceCommon.alias.write) {
if (sourceCommon?.alias?.write) {
if (!targetCommon) {
logger.error(
`${logNamespace}target for "${sourceId}" does not exist for "write" function: "${sourceCommon.alias.write}"`
);
return null;
}
try {
// process the value here
const func = new Function(
'val',
'type',
'min',
'max',
'tType',
'tMin',
'tMax',
`return ${sourceCommon.alias.write}`
);
state.val = func(
state.val,
sourceCommon.type,
sourceCommon.min,
sourceCommon.max,
targetCommon.type,
targetCommon.min,
targetCommon.max
);
state.val = applyAliasTransformer({
transformer: sourceCommon.alias.write,
firstCommon: sourceCommon,
secondCommon: targetCommon,
isRead: false,
state
});
} catch (e) {
logger.error(
`${logNamespace} Invalid write function for "${sourceId}": "${sourceCommon.alias.write}" => ${e.message}`
`${logNamespace}Invalid write function for "${sourceId}": "${sourceCommon.alias.write}" => ${e.message}`
);
return null;
}
}

if (targetCommon && typeof state.val !== targetCommon.type && state.val !== null) {
if (targetCommon.type === 'boolean') {
const lowerVal = typeof state.val === 'string' ? state.val.toLowerCase() : state.val;
if (lowerVal === 'off' || lowerVal === 'aus' || state.val === '0') {
state.val = false;
} else {
// this also handles strings like "EIN" or such that will be true
state.val = !!state.val;
}
} else if (targetCommon.type === 'number') {
state.val = parseFloat(state.val as any);
} else if (targetCommon.type === 'string') {
state.val = state.val.toString();
}
}

// auto-scaling, only if val not null and unit for target (x)or source is %
if (
((targetCommon && targetCommon.alias && !targetCommon.alias.read) ||
(sourceCommon && sourceCommon.alias && !sourceCommon.alias.write)) &&
state.val !== null
) {
if (
targetCommon &&
targetCommon.type === 'number' &&
targetCommon.unit === '%' &&
sourceCommon &&
sourceCommon.type === 'number' &&
sourceCommon.unit !== '%' &&
sourceCommon.min !== undefined &&
sourceCommon.max !== undefined
) {
// scale target between 0 and 100 % based on sources min/max
state.val = (((state.val as any) - sourceCommon.min) / (sourceCommon.max - sourceCommon.min)) * 100;
} else if (
sourceCommon &&
sourceCommon.type === 'number' &&
sourceCommon.unit === '%' &&
targetCommon &&
targetCommon.unit !== '%' &&
targetCommon.type === 'number' &&
targetCommon.min !== undefined &&
targetCommon.max !== undefined
) {
// scale target based on its min/max by its source (assuming source is meant to be 0 - 100 %)
state.val = ((targetCommon.max - targetCommon.min) * (state.val as any)) / 100 + targetCommon.min;
}
}
state.val = applyAliasConvenienceConversion({ state, targetCommon });
state.val = applyAliasAutoScaling({ state, sourceCommon, targetCommon });

return state;
}
Expand Down
14 changes: 7 additions & 7 deletions packages/controller/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2972,9 +2972,9 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
);

msg.callback && msg.from && sendTo(msg.from, msg.command, {}, msg.callback);
} catch (error) {
logger.error(`${hostLogPrefix} Cannot write zip file as folder: ${error}`);
msg.callback && msg.from && sendTo(msg.from, msg.command, { error }, msg.callback);
} catch (e) {
logger.error(`${hostLogPrefix} Cannot write zip file as folder: ${e.message}`);
msg.callback && msg.from && sendTo(msg.from, msg.command, { error: e.message }, msg.callback);
}
break;

Expand All @@ -2989,7 +2989,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
msg.message.options
);
} catch (e) {
sendTo(msg.from, msg.command, { error: e }, msg.callback);
sendTo(msg.from, msg.command, { error: e.message }, msg.callback);
return;
}

Expand All @@ -3004,7 +3004,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
buff
);
} catch (e) {
sendTo(msg.from, msg.command, { error: e }, msg.callback);
sendTo(msg.from, msg.command, { error: e.message }, msg.callback);
return;
}

Expand All @@ -3023,7 +3023,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi

states!.setBinaryState(`${hostObjectPrefix}.zip.${msg.message.link}`, buff, err => {
if (err) {
sendTo(msg.from, msg.command, { error: err }, msg.callback);
sendTo(msg.from, msg.command, { error: err.message }, msg.callback);
} else {
sendTo(
msg.from,
Expand All @@ -3049,7 +3049,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
msg.message.adapter,
Buffer.from(msg.message.data || '', 'base64'),
msg.message.options,
error => msg.callback && msg.from && sendTo(msg.from, msg.command, { error }, msg.callback)
err => msg.callback && msg.from && sendTo(msg.from, msg.command, { error: err?.message }, msg.callback)
);
break;

Expand Down
Loading