signalapp/Signal-Server · error · BackupInvalidArgumentException

unknown cdn

Error message

unknown cdn

What it means

BackupInvalidArgumentException thrown by BackupManager.generateReadAuth when cdnNumber is not 3. Read authorization for backup media is only issued for CDN 3 (the CDN backing Signal backups); other CDN numbers are unknown to this code path. The javadoc explicitly documents this as an invalid cdnNumber input.

Solutions

  1. Pass cdnNumber 3 for all backup read-auth requests
  2. Route legacy attachment downloads (cdn 0/2) through the attachments code path instead of the backup path
  3. Parse the cdn number from the current server-provided backup descriptor rather than hardcoding it
  4. Update outdated clients that predate backup CDN 3

Example fix

// before
int cdn = attachment.cdn; // 0 for legacy attachments
generateReadAuth(backupUser, cdn); // 400: unknown cdn
// after
if (cdn == 3) {
  generateReadAuth(backupUser, cdn);
} else {
  downloadViaLegacyAttachmentApi(attachment);
}
Defensive patterns

Strategy: validation

Validate before calling

if (cdnNumber != 3) {
  throw new IllegalArgumentException("backup read auth requires cdn 3, got " + cdnNumber);
}

Try / catch

try { generateReadAuth(backupUser, cdn); }
catch (BackupInvalidArgumentException e) {
  if (e.getMessage().contains("unknown cdn")) { useLegacyAttachmentDownload(); } else { throw e; }
}

Prevention

When it happens

Trigger: Requesting read auth for backup objects while passing cdn=0, 1, or 2 — e.g. reusing constants from legacy attachment CDN paths, or echoing a cdn value parsed from an old backup descriptor.

Common situations: Client code sharing one 'download attachment' helper across legacy attachments (CDN 0/2) and backups (CDN 3); stale backup manifests referencing old CDN numbers; hardcoded CDN constants that drifted between client versions.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/d71253d6d185c2ad. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java:421

  public record StorageDescriptor(int cdn, byte[] key) {}

  public record StorageDescriptorWithLength(int cdn, byte[] key, long length) {}

  /**
   * Generate credentials that can be used to read from the backup CDN
   *
   * @param backupUser an already ZK authenticated backup user
   * @param cdnNumber  the cdn number to get backup credentials for
   * @return A map of headers to include with CDN requests
   * @throws BackupPermissionException if the credential does not have the correct level
   * @throws BackupInvalidArgumentException if the provided cdnNumber is invalid
   */
  public Map<String, String> generateReadAuth(final AuthenticatedBackupUser backupUser, final int cdnNumber)
      throws BackupInvalidArgumentException, BackupPermissionException {
    checkBackupLevel(backupUser, BackupLevel.FREE);
    if (cdnNumber != 3) {
      throw new BackupInvalidArgumentException("unknown cdn");
    }
    return cdn3BackupCredentialGenerator.readHeaders(backupUser.backupDir());
  }

  /**
   * Generate credentials that can be used with SVRB
   *
   * @param backupUser an already ZK authenticated backup user
   * @return the credential that may be used with SVRB
   * @throws BackupPermissionException if the credential does not have the correct level
   * @throws BackupWrongCredentialTypeException if the credential does not have the messages type
   */
  public ExternalServiceCredentials generateSvrbAuth(final AuthenticatedBackupUser backupUser)
      throws BackupPermissionException, BackupWrongCredentialTypeException {
    checkBackupLevel(backupUser, BackupLevel.FREE);
    // Clients may only use SVRB with their messages backup-id
    checkBackupCredentialType(backupUser, BackupCredentialType.MESSAGES);
    return secureValueRecoveryBCredentialsGenerator.generateFor(svrbIdentifier(backupUser));

View on GitHub (pinned to 100ab61c82)