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:
| Constant | String Value | Write? | Factory Method |
|---|---|---|---|
UPLOAD | "UPLOAD" | ✅ Write | FileOperation.upload(username, filename, data) |
DOWNLOAD | "DOWNLOAD" | ❌ Read | FileOperation.download(username, filename) |
DELETE | "DELETE" | ✅ Write | FileOperation.delete(username, filename) |
RENAME | "RENAME" | ✅ Write | FileOperation.rename(username, filename, newFilename) |
SEARCH | "SEARCH" | ❌ Read | FileOperation.search(username, keyword) |
LIST | "LIST" | ❌ Read | FileOperation.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
DELETEwithfileData) - Auto-populates
timestampandnonce
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?
messageIduniquely identifies the message for ACK tracking ("have I received an ACK from Node 2 for message abc-123?")timestamporders 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
| Class | Serializable? | Mutable? | Travels over RMI? |
|---|---|---|---|
FileOperation | ✅ Yes | ❌ Immutable | Client → Node, Node → Node |
OperationResult | ✅ Yes | ❌ Immutable | Node → Client |
ClockMessage | ✅ Yes | ❌ Immutable | Node → Node (multicast) |
UserCredentials | ✅ Yes | ❌ Immutable | Client → AuthService |
Next: → Node Layer Deep-Dive