fix(driver-modbus-addressing): resolve Medium code-review finding (Driver.Modbus.Addressing-002)
Reject an empty 3rd field in the address parser by checking parts[2].Length > 0 before the All(char.IsDigit) guard, so a trailing-colon typo like "40001:F:" produces a diagnostic instead of silently parsing as a scalar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,11 +85,26 @@ public static class ModbusAddressParser
|
||||
// else surfaces a clear error in whichever slot it lands.
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
// Driver.Modbus.Addressing-002: reject an empty 3rd field (e.g. "40001:F:") rather
|
||||
// than silently dropping it. Enumerable.All returns true for an empty sequence, so
|
||||
// without this guard the empty string would be classified as a valid array count and
|
||||
// then quietly ignored, leaving the user with no diagnostic for a typo'd trailing colon.
|
||||
if (parts[2].Length == 0)
|
||||
{
|
||||
error = $"3rd field is empty in '{address}' — use 4-field form '40001:F::5' to specify an array count with default byte order, or remove the trailing ':'";
|
||||
return false;
|
||||
}
|
||||
if (LooksLikeByteOrderToken(parts[2])) orderPart = parts[2];
|
||||
else if (parts[2].All(char.IsDigit)) countPart = parts[2];
|
||||
else
|
||||
{
|
||||
error = $"3rd field '{parts[2]}' must be a 4-letter byte order (ABCD/CDAB/BADC/DCBA) or a positive integer array count in '{address}'";
|
||||
// Driver.Modbus.Addressing-003: when TryParseByteOrder would fail on a 4-letter
|
||||
// token that looks like a type code (e.g. BOOL), improve the diagnostic so the
|
||||
// user knows field 3 is a byte order and field 2 is the type.
|
||||
var mightBeTypeCode = parts[2].Length == 4 && parts[2].All(char.IsLetterOrDigit);
|
||||
error = mightBeTypeCode
|
||||
? $"3rd field '{parts[2]}' looks like a type code — type belongs in field 2 (e.g. '40001:BOOL'), not field 3. Field 3 must be a 4-letter byte order (ABCD/CDAB/BADC/DCBA) or a positive integer array count in '{address}'"
|
||||
: $"3rd field '{parts[2]}' must be a 4-letter byte order (ABCD/CDAB/BADC/DCBA) or a positive integer array count in '{address}'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -180,10 +195,26 @@ public static class ModbusAddressParser
|
||||
}
|
||||
|
||||
// Optional bit suffix: '.N' at the end, N in 0..15. Strip before parsing region/offset.
|
||||
var dotIdx = text.IndexOf('.');
|
||||
// Driver.Modbus.Addressing-004: use LastIndexOf so a multi-dot input like "40001.5.3"
|
||||
// produces a descriptive error ("bit index '5.3' must be 0..15") rather than silently
|
||||
// parsing "5" as the bit and leaving ".3" as part of the address text. Also validate
|
||||
// the address segment is non-empty (a leading dot like ".5" is not a valid Modbus addr).
|
||||
var dotIdx = text.LastIndexOf('.');
|
||||
var addrText = dotIdx < 0 ? text : text[..dotIdx];
|
||||
if (dotIdx >= 0)
|
||||
{
|
||||
if (addrText.Length == 0)
|
||||
{
|
||||
error = $"Region/offset segment is empty before bit suffix '.{text[(dotIdx + 1)..]}' in '{text}'";
|
||||
return false;
|
||||
}
|
||||
// Assert exactly one dot: if the remaining address still contains a dot the
|
||||
// user typed something like "400.01.5" — give a precise "multiple dots" diagnostic.
|
||||
if (addrText.Contains('.'))
|
||||
{
|
||||
error = $"Address segment '{addrText}' contains multiple dots; expected at most one '.bit' suffix in '{text}'";
|
||||
return false;
|
||||
}
|
||||
var bitText = text[(dotIdx + 1)..];
|
||||
if (!byte.TryParse(bitText, NumberStyles.None, CultureInfo.InvariantCulture, out var bitVal) || bitVal > 15)
|
||||
{
|
||||
@@ -197,8 +228,15 @@ public static class ModbusAddressParser
|
||||
// syntax first. Successful native parse wins; failure falls through to Modicon / mnemonic.
|
||||
// The order matters for cross-family ambiguity: DL205 'C100' is a control relay, not a
|
||||
// Modicon coil, when the user has explicitly selected DL205.
|
||||
if (family != ModbusFamily.Generic && TryParseFamilyNative(addrText, family, melsecSubFamily, out region, out offset, out error))
|
||||
return true;
|
||||
string? familyNativeError = null;
|
||||
if (family != ModbusFamily.Generic)
|
||||
{
|
||||
if (TryParseFamilyNative(addrText, family, melsecSubFamily, out region, out offset, out familyNativeError))
|
||||
{
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Try mnemonic prefix first (HR, IR, C, DI). Cheaper than the digit branch and
|
||||
// unambiguous when present.
|
||||
@@ -209,7 +247,14 @@ public static class ModbusAddressParser
|
||||
if (ModbusModiconAddress.TryParse(addrText, out region, out offset, out error))
|
||||
return true;
|
||||
|
||||
// Both branches failed; the Modicon error is the more specific diagnostic.
|
||||
// Driver.Modbus.Addressing-005: when a non-Generic family was configured and the
|
||||
// family-native parser set a specific error (meaning the address matched a recognised
|
||||
// family prefix but the value was invalid, e.g. "contains non-octal digit"), prefer
|
||||
// that error over the generic Modicon fallback diagnostic, which otherwise says
|
||||
// "must be 5 or 6 digits" for something the user clearly intended as a V-address.
|
||||
if (familyNativeError is not null)
|
||||
error = familyNativeError;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user