{"record":{"id":"d5efe3c1ab181c27","repo":"signalapp/Signal-Server","slug":"invalid-length","errorCode":null,"errorMessage":"Invalid length","messagePattern":"Invalid length","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java","lineNumber":770,"sourceCode":"\n      @NotNull\n      @Valid final ArchiveController.CopyMediaRequest copyMediaRequest)\n      throws BackupFailedZkAuthenticationException, BackupWrongCredentialTypeException, BackupPermissionException, BackupInvalidArgumentException {\n    if (account.isPresent()) {\n      throw new BadRequestException(\"must not use authenticated connection for anonymous operations\");\n    }\n\n    final AuthenticatedBackupUser backupUser =\n        backupManager.authenticateBackupUser(presentation.presentation, signature.signature, userAgent);\n    final BackupManager.CopyQuota copyQuota =\n        backupManager.getCopyQuota(backupUser, List.of(copyMediaRequest.toCopyParameters()), maxAttachmentSize);\n    final CopyResult copyResult = backupManager.copyToBackup(copyQuota).next()\n            .blockOptional()\n            .orElseThrow(() -> new IllegalStateException(\"Non empty copy request must return result\"));\n    backupMetrics.updateCopyCounter(copyResult, UserAgentTagUtil.getPlatformTag(userAgent));\n    return switch (copyResult.outcome()) {\n      case SUCCESS -> new CopyMediaResponse(copyResult.cdn());\n      case SOURCE_WRONG_LENGTH -> throw new BadRequestException(\"Invalid length\");\n      case SOURCE_NOT_FOUND -> throw new ClientErrorException(\"Source object not found\", Response.Status.GONE);\n      case OUT_OF_QUOTA ->\n          throw new ClientErrorException(\"Media quota exhausted\", Response.Status.REQUEST_ENTITY_TOO_LARGE);\n    };\n  }\n\n  public record CopyMediaBatchRequest(\n      @Schema(description = \"A list of media objects to copy from the attachments CDN to the backup CDN\")\n      @NotNull\n      @Size(min = 1, max = 1000)\n      List<@Valid CopyMediaRequest> items) {}\n\n  public record CopyMediaBatchResponse(\n\n      @Schema(description = \"Detailed outcome information for each copy request in the batch\")\n      List<Entry> responses) {\n\n    public record Entry(","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java#L752-L788","documentation":"After performing the media copy, the controller maps the CopyResult outcome SOURCE_WRONG_LENGTH to BadRequestException(\"Invalid length\"): the source object stored on the CDN has a size that differs from the expected length supplied in the copy request, so the integrity-checked copy is refused.","triggerScenarios":"Calling copyMedia with CopyMediaObject whose source length/expected plaintext length does not match the actual stored object's size.","commonSituations":"Client DB retaining stale attachment metadata (size recorded before a re-upload); partially uploaded or truncated source objects; migrating from an older client that recorded sizes differently; CDN objects modified or replaced.","solutions":["Re-fetch the attachment's actual object size from the CDN and correct the length in CopyMediaObject before copying.","Re-upload the source attachment if the stored object is truncated or corrupted, then retry the copy.","Refresh local attachment metadata (size, digest) from the message sender or server.","If the source is unrecoverable, skip the copy and treat the attachment as missing."],"exampleFix":"// before\ncopyRequest = new CopyMediaObject(oldMetadata.size, ...); // stale size -> 400 Invalid length\n// after\nlong actualSize = headObject(sourceCdnKey).contentLength();\ncopyRequest = new CopyMediaObject(actualSize, ...);","handlingStrategy":"try-catch","validationCode":"long actualSize = headSourceObject(cdn, key).contentLength();\nif (actualSize != expectedLength) {\n  refreshMetadataThenCopy(); // fix length before calling copyMedia\n}","typeGuard":null,"tryCatchPattern":"try {\n  copyMedia(request);\n} catch (BadRequestException e) {\n  if (\"Invalid length\".equals(e.getMessage())) {\n    reFetchObjectSizeAndRetry();\n  } else throw e;\n}","preventionTips":["Keep attachment size metadata in sync with actual CDN objects.","Re-upload truncated or partially written source objects before copying.","Verify sizes/digests when migrating between client versions."],"tags":["http-400","validation","cdn","signal"],"backgroundTag":"invalid-argument-value","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}