{"record":{"id":"049086054c9b17e2","repo":"signalapp/Signal-Server","slug":"field-value-is-expected-to-be-within-the-d-d","errorCode":null,"errorMessage":"field value is expected to be within the [%d, %d] range","messagePattern":"field value is expected to be within the \\[(.+?), (.+?)\\] range","errorType":"validation","errorClass":"FieldValidationException","httpStatus":null,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/grpc/validators/RangeFieldValidator.java","lineNumber":50,"sourceCode":"        Descriptors.FieldDescriptor.Type.SINT64\n    ), MissingOptionalAction.SUCCEED, false);\n  }\n\n  @Override\n  protected Range resolveExtensionValue(final Object extensionValue) {\n    final ValueRangeConstraint rangeConstraint = (ValueRangeConstraint) extensionValue;\n    final long min = rangeConstraint.hasMin() ? rangeConstraint.getMin() : Long.MIN_VALUE;\n    final long max = rangeConstraint.hasMax() ? rangeConstraint.getMax() : Long.MAX_VALUE;\n    return new Range(min, max);\n  }\n\n  @Override\n  protected void validateIntegerNumber(\n      final Range range,\n      final long fieldValue,\n      final Descriptors.FieldDescriptor.Type type) throws FieldValidationException {\n    if (fieldValue < 0 && UNSIGNED_TYPES.contains(type)) {\n      throw new FieldValidationException(\"field value is expected to be within the [%d, %d] range\".formatted(\n          range.min(), range.max()));\n    }\n    if (fieldValue < range.min() || fieldValue > range.max()) {\n      throw new FieldValidationException(\"field value is [%d] but expected to be within the [%d, %d] range\".formatted(\n          fieldValue, range.min(), range.max()));\n    }\n  }\n}\n","sourceCodeStart":32,"sourceCodeEnd":59,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/grpc/validators/RangeFieldValidator.java#L32-L59","documentation":"RangeFieldValidator.validateIntegerNumber throws FieldValidationException when a negative value is supplied for a proto field whose type is an unsigned integer type (uint32/uint64), since negative numbers can never be in an unsigned range. The message shows the field's configured [min, max] range.","triggerScenarios":"Passing a negative long into a proto field declared uint32/uint64 and covered by a range annotation in a Signal gRPC request; e.g. sending -1 for a device ID or counter.","commonSituations":"Java int arithmetic producing negatives (overflow, subtraction) before conversion; clients using signed types in their own language and casting; sentinel values like -1 used for 'unknown'.","solutions":["Clamp or reject negative values before setting unsigned proto fields","Use sentinel values >= 0 (or optional fields) instead of -1 for 'unknown'","Verify the field type in the proto; switch to a signed int type if negatives are legitimate"],"exampleFix":"// before\nbuilder.setDeviceId(deviceId); // deviceId may be -1\n// after\nif (deviceId < 0) {\n  throw new IllegalArgumentException(\"deviceId must be non-negative\");\n}\nbuilder.setDeviceId(deviceId);","handlingStrategy":"validation","validationCode":"if (value < 0) throw new IllegalArgumentException(\"unsigned proto field cannot be negative\");","typeGuard":"boolean isValidUnsigned(long v) { return v >= 0; }","tryCatchPattern":"try { stub.call(request); } catch (StatusRuntimeException e) { if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT && e.getMessage().contains(\"range\")) { /* handle unsigned violation */ } }","preventionTips":["Never use -1 as a sentinel for unsigned proto fields","Check field declared types (uint32/uint64) in the proto","Validate arithmetic results before assigning to unsigned fields"],"tags":["grpc","validation","range","unsigned-integer"],"backgroundTag":"value-out-of-range","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T14:17:13.074Z"}