Skip to main content

Common Layer — Data Transfer Objects

The com.dfs.common package contains the four classes that travel over the network. Everything else in the system depends on these.


FileOperation

File: src/main/java/com/dfs/common/FileOperation.java (235 lines)

This is the single message type that clients send to nodes. It represents one of six operations:

ConstantString ValueWrite?Factory Method
UPLOAD"UPLOAD"✅ WriteFileOperation.upload(username, filename, data)
DOWNLOAD"DOWNLOAD"❌ ReadFileOperation.download(username, filename)
DELETE"DELETE"✅ WriteFileOperation.delete(username, filename)
RENAME"RENAME"✅ WriteFileOperation.rename(username, filename, newFilename)
SEARCH"SEARCH"❌ ReadFileOperation.search(username, keyword)
LIST"LIST"❌ ReadFileOperation.list(username)

Key Design Decisions

Immutable: All fields are final. Once created, a FileOperation can't be modified — eliminating an entire class of bugs where an operation is accidentally mutated between creation and execution.

Factory methods, not constructors: The constructor is private. Clients use static factories like FileOperation.upload(...). This:

  • Makes the intent obvious at the call site
  • Prevents invalid states (like DELETE with fileData)
  • Auto-populates timestamp and nonce

Auto-populated security fields:

private FileOperation(...) {
// ...
this.timestamp = System.currentTimeMillis(); // Replay: freshness check
this.nonce = UUID.randomUUID().toString(); // Replay: uniqueness check
}

Validation

The validate() method is called server-side before executing any operation:

public boolean validate() {
if (!VALID_OPERATIONS.contains(operationType)) return false;

if (!LIST.equals(operationType)) {
if (filename == null || filename.isBlank()) return false;
// Path-traversal guard — no escaping the storage directory!
if (filename.contains("..") || filename.contains("/") || filename.contains("\\"))
return false;
}

// fileData should only be present on UPLOAD
if (fileData != null && !UPLOAD.equals(operationType)) return false;

return true;
}

The path-traversal check prevents attacks like ../../etc/passwd that could let a malicious client read or overwrite files outside the storage directory.


OperationResult

File: src/main/java/com/dfs/common/OperationResult.java (~50 lines)

The server's response to every client operation. Simple but carefully designed:

public class OperationResult implements Serializable {
private final boolean success;
private final String message; // Human-readable status
private final byte[] fileData; // For DOWNLOAD results
private final List<String> fileList; // For LIST and SEARCH results
}

Design principle: One class for all responses. This means the client has a single, predictable return type and can pattern-match on success + check which fields are populated.


ClockMessage

File: src/main/java/com/dfs/common/ClockMessage.java (~60 lines)

The wrapper that carries a write operation through the TO-Multicast protocol:

public class ClockMessage implements Serializable {
private final String messageId; // UUID — globally unique
private final int senderId; // Which node originated this
private final long timestamp; // Lamport clock value
private final FileOperation operation; // The actual operation
}

Why messageId AND timestamp?

  • messageId uniquely identifies the message for ACK tracking ("have I received an ACK from Node 2 for message abc-123?")
  • timestamp orders messages in the priority queue ("deliver ts=5 before ts=7")

They serve completely different purposes.

Why is senderId needed?
For tie-breaking. If two nodes generate messages with the same timestamp, the queue sorts by senderId to deterministically break the tie.


UserCredentials

File: src/main/java/com/dfs/common/UserCredentials.java (~30 lines)

The simplest class in the project:

public class UserCredentials implements Serializable {
private final String username;
private final String password; // Plaintext on the wire — hashed server-side
}

Security note: The password travels in plaintext, but it's inside an mTLS-encrypted channel. Without mTLS (the vulnerable version), this is a critical vulnerability — an eavesdropper can read the password directly.


Summary

ClassSerializable?Mutable?Travels over RMI?
FileOperation✅ Yes❌ ImmutableClient → Node, Node → Node
OperationResult✅ Yes❌ ImmutableNode → Client
ClockMessage✅ Yes❌ ImmutableNode → Node (multicast)
UserCredentials✅ Yes❌ ImmutableClient → AuthService

Next: → Node Layer Deep-Dive