# GuruRMM Server - Agent Tunnel Protocol Update

## Summary
Updated the server's WebSocket protocol to handle tunnel messages FROM the agent, completing the bidirectional tunnel communication.

**Status:** ✅ Complete - Code compiles successfully with no errors.

---

## Problem Statement

The agent was sending `TunnelReady`, `TunnelData`, and `TunnelError` messages, but the server's `AgentMessage` enum didn't have these variants. This would cause deserialization failures when agents attempted to send tunnel messages.

**Error that would occur:**
```
Error: Failed to deserialize agent message: unknown variant `tunnel_ready`
```

---

## Changes Made

### 1. Updated AgentMessage Enum
**File:** `server/src/ws/mod.rs` (lines 80-91)

**Added three new variants:**

```rust
pub enum AgentMessage {
    Auth(AuthPayload),
    Metrics(MetricsPayload),
    NetworkState(NetworkStatePayload),
    CommandResult(CommandResultPayload),
    WatchdogEvent(WatchdogEventPayload),
    UpdateResult(UpdateResultPayload),
    Heartbeat,
    // NEW: Tunnel messages from agent
    TunnelReady { session_id: String },
    TunnelData { channel_id: String, data: TunnelDataPayload },
    TunnelError { channel_id: String, error: String },
}
```

**Serialization format:**
- Uses `#[serde(tag = "type", content = "payload")]` for tagged enum
- Uses `#[serde(rename_all = "snake_case")]` for JSON field names
- Matches agent's message format exactly

---

### 2. Added Message Handlers
**File:** `server/src/ws/mod.rs` (in `handle_agent_message` function, after UpdateResult handler)

#### TunnelReady Handler
```rust
AgentMessage::TunnelReady { session_id } => {
    info!(
        "Agent {} tunnel ready: session_id={}",
        agent_id, session_id
    );

    // Update session activity timestamp
    if let Err(e) = db::update_session_activity(&state.db, &session_id).await {
        error!(
            "Failed to update session activity for {}: {}",
            session_id, e
        );
    }
}
```

**Purpose:**
- Confirms agent received `TunnelOpen` and is ready
- Updates `last_activity` timestamp in database
- Logs successful tunnel establishment

**Future Enhancement (Phase 2):**
- Could mark session status as "ready" (vs "active" but not ready)
- Could notify waiting clients that tunnel is available

---

#### TunnelData Handler
```rust
AgentMessage::TunnelData { channel_id, data } => {
    debug!(
        "Received tunnel data from agent {}: channel_id={}, type={:?}",
        agent_id, channel_id, data
    );

    // Phase 2: Forward data to connected clients via WebSocket or REST API
    // For now, just log the data
    match data {
        TunnelDataPayload::TerminalOutput { stdout, stderr, exit_code } => {
            if !stdout.is_empty() {
                debug!("Terminal stdout: {}", stdout.trim());
            }
            if !stderr.is_empty() {
                debug!("Terminal stderr: {}", stderr.trim());
            }
            if let Some(code) = exit_code {
                debug!("Terminal exit code: {}", code);
            }
        }
        TunnelDataPayload::Terminal { command } => {
            debug!("Terminal command echo: {}", command);
        }
    }
}
```

**Purpose:**
- Receives terminal output from agent
- Logs output for debugging
- **Placeholder for Phase 2:** Will forward to connected clients

**Phase 2 Implementation:**
- Store output in database or in-memory buffer
- Forward to WebSocket clients listening on this channel
- Or provide REST endpoint to poll for output

---

#### TunnelError Handler
```rust
AgentMessage::TunnelError { channel_id, error } => {
    error!(
        "Tunnel error from agent {}: channel_id={}, error={}",
        agent_id, channel_id, error
    );

    // Phase 2: Forward error to connected clients
    // For now, just log the error
}
```

**Purpose:**
- Receives error messages from agent tunnel operations
- Logs errors for monitoring and debugging
- **Placeholder for Phase 2:** Will notify clients of errors

**Phase 2 Implementation:**
- Forward error to connected clients
- Mark channel as failed in database
- Potentially close tunnel session on critical errors

---

## Message Flow

### Tunnel Lifecycle

**1. Open Tunnel (Server → Agent):**
```
Client HTTP Request → Server API → Database Insert
                                 ↓
                    Server WebSocket → Agent (TunnelOpen)
```

**2. Tunnel Ready (Agent → Server):**
```
Agent (TunnelReady) → Server WebSocket → Database Update
                                       ↓
                                    Log Success
```

**3. Terminal Command (Phase 2):**
```
Client Request → Server (TunnelData/Terminal) → Agent
                                               ↓
                        Agent Executes Command
                                               ↓
Agent (TunnelData/TerminalOutput) → Server → Client
```

**4. Error Handling:**
```
Agent Error → Agent (TunnelError) → Server → Log
                                           ↓
                               (Phase 2: Notify Client)
```

**5. Close Tunnel:**
```
Client HTTP Request → Server API → Server (TunnelClose) → Agent
                                 ↓
                         Database Update
```

**6. Agent Disconnect:**
```
Agent WebSocket Close → Server Cleanup → Database Close All Sessions
```

---

## Protocol Verification

### Agent Messages (FROM Agent to Server)
✅ `Auth` - Authentication handshake
✅ `Metrics` - System metrics reporting
✅ `NetworkState` - Network interface updates
✅ `CommandResult` - Command execution results
✅ `WatchdogEvent` - Service monitoring events
✅ `UpdateResult` - Agent update status
✅ `Heartbeat` - Keep-alive ping
✅ **`TunnelReady`** - Tunnel established (NEW)
✅ **`TunnelData`** - Tunnel data payload (NEW)
✅ **`TunnelError`** - Tunnel error message (NEW)

### Server Messages (FROM Server to Agent)
✅ `AuthAck` - Authentication response
✅ `Command` - Execute command
✅ `ConfigUpdate` - Configuration change
✅ `Update` - Agent update instruction
✅ `Ack` - Generic acknowledgment
✅ `Error` - Error message
✅ **`TunnelOpen`** - Open tunnel session (Phase 1)
✅ **`TunnelClose`** - Close tunnel session (Phase 1)
✅ **`TunnelData`** - Tunnel data payload (Phase 1)

---

## Data Structures

### TunnelDataPayload (Shared by Agent and Server)
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "payload")]
#[serde(rename_all = "snake_case")]
pub enum TunnelDataPayload {
    /// Terminal command execution (Phase 1)
    Terminal { command: String },
    /// Terminal output response
    TerminalOutput {
        stdout: String,
        stderr: String,
        exit_code: Option<i32>,
    },
}
```

**Note:** This enum is already defined in `ws/mod.rs` and is used by both `ServerMessage::TunnelData` and `AgentMessage::TunnelData`.

---

## Testing Validation

### 1. TunnelReady Message
**Agent sends:**
```json
{
  "type": "tunnel_ready",
  "payload": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
```

**Expected server behavior:**
- Deserializes successfully
- Logs: `Agent <uuid> tunnel ready: session_id=<uuid>`
- Updates `tech_sessions.last_activity` timestamp
- No errors

---

### 2. TunnelData Message (Terminal Output)
**Agent sends:**
```json
{
  "type": "tunnel_data",
  "payload": {
    "channel_id": "terminal-1",
    "data": {
      "type": "terminal_output",
      "payload": {
        "stdout": "Hello, World!\n",
        "stderr": "",
        "exit_code": 0
      }
    }
  }
}
```

**Expected server behavior:**
- Deserializes successfully
- Logs: `Received tunnel data from agent <uuid>: channel_id=terminal-1`
- Logs: `Terminal stdout: Hello, World!`
- Logs: `Terminal exit code: 0`

---

### 3. TunnelError Message
**Agent sends:**
```json
{
  "type": "tunnel_error",
  "payload": {
    "channel_id": "terminal-1",
    "error": "Failed to execute command: permission denied"
  }
}
```

**Expected server behavior:**
- Deserializes successfully
- Logs error: `Tunnel error from agent <uuid>: channel_id=terminal-1, error=Failed to execute command: permission denied`

---

## Compilation Status

**Result:** ✅ SUCCESS

```bash
$ cargo check
    Checking gururmm-server v0.2.0
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.50s
```

**Notes:**
- Zero compilation errors
- All tunnel message variants properly integrated
- Existing warnings unrelated to tunnel changes

---

## Phase 2 Requirements

To complete the tunnel feature, Phase 2 needs:

### Server-Side:
1. **Client WebSocket endpoint** for tunnel output streaming
   - Route: `GET /api/v1/tunnel/:session_id/stream`
   - Streams terminal output in real-time

2. **Send command endpoint** (HTTP or WebSocket)
   - Route: `POST /api/v1/tunnel/:session_id/command`
   - Body: `{ "command": "ls -la" }`
   - Sends `TunnelData(Terminal)` to agent

3. **Output buffering** (optional)
   - Store recent output in memory or database
   - Allow clients to retrieve missed output

4. **Client connection tracking**
   - Track which clients are listening to which sessions
   - Forward output only to connected clients

### Agent-Side (Already Complete):
✅ `TunnelOpen` handler
✅ `TunnelClose` handler
✅ `TunnelData` handler for terminal commands
✅ Terminal command execution
✅ Output capture and streaming

---

## Security Considerations

### Already Implemented:
✅ Session ownership verification (only tunnel creator can interact)
✅ JWT authentication required for all endpoints
✅ Foreign key constraints (sessions tied to users)
✅ Automatic session cleanup on agent disconnect

### Phase 2 Considerations:
- Rate limiting on command execution (prevent abuse)
- Command whitelisting/blacklisting (security policy)
- Audit logging of all commands executed
- Session timeout for idle tunnels
- Maximum concurrent sessions per user

---

## Database Schema

Current schema already supports the protocol:

```sql
CREATE TABLE tech_sessions (
    id SERIAL PRIMARY KEY,
    session_id VARCHAR(36) UNIQUE NOT NULL,
    tech_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
    opened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    closed_at TIMESTAMPTZ,
    status VARCHAR(20) NOT NULL DEFAULT 'active'
);
```

**Notes:**
- `last_activity` updated on `TunnelReady` and future command activity
- `status` can be extended: 'active', 'ready', 'closed', 'error'
- `tunnel_audit` table ready for Phase 2 command logging

---

## Files Modified

1. **server/src/ws/mod.rs**
   - Added 3 new `AgentMessage` variants
   - Added handlers for `TunnelReady`, `TunnelData`, `TunnelError`
   - Uses existing `TunnelDataPayload` enum (already defined)

**Total lines changed:** ~70 lines added

---

## Next Steps

1. **Test Protocol Integration**
   - Mock agent sending TunnelReady, TunnelData, TunnelError
   - Verify server logs show correct deserialization
   - Verify database updates (last_activity timestamp)

2. **Phase 2 Server Implementation**
   - Client WebSocket endpoint for output streaming
   - Command execution endpoint
   - Client connection management
   - Output buffering/forwarding

3. **End-to-End Testing**
   - Full tunnel lifecycle with real agent
   - Command execution and output streaming
   - Error handling and edge cases
   - Performance testing (concurrent sessions)

---

**Last Updated:** 2026-04-14
**Status:** Protocol update complete, ready for Phase 2 implementation
