# GuruRMM Tunnel Protocol - Quick Reference

## Message Types

### Server → Agent

| Message | Payload | Purpose |
|---------|---------|---------|
| `TunnelOpen` | `{ session_id: String, tech_id: Uuid }` | Open tunnel session |
| `TunnelClose` | `{ session_id: String }` | Close tunnel session |
| `TunnelData` | `{ channel_id: String, data: TunnelDataPayload }` | Send command/data |

### Agent → Server

| Message | Payload | Purpose |
|---------|---------|---------|
| `TunnelReady` | `{ session_id: String }` | Confirm tunnel ready |
| `TunnelData` | `{ channel_id: String, data: TunnelDataPayload }` | Return output/data |
| `TunnelError` | `{ channel_id: String, error: String }` | Report error |

### TunnelDataPayload (Both Directions)

| Variant | Fields | Direction | Purpose |
|---------|--------|-----------|---------|
| `Terminal` | `{ command: String }` | Server → Agent | Execute terminal command |
| `TerminalOutput` | `{ stdout: String, stderr: String, exit_code: Option<i32> }` | Agent → Server | Return command output |

---

## Message Flow Examples

### 1. Open Tunnel
```
Client → Server API: POST /api/v1/tunnel/open {"agent_id":"..."}
Server → Agent WS:   {"type":"tunnel_open","payload":{"session_id":"...","tech_id":"..."}}
Agent → Server WS:   {"type":"tunnel_ready","payload":{"session_id":"..."}}
Server: Updates last_activity, logs success
```

### 2. Execute Command (Phase 2)
```
Client → Server API: POST /api/v1/tunnel/:session_id/command {"command":"ls -la"}
Server → Agent WS:   {"type":"tunnel_data","payload":{"channel_id":"...","data":{"type":"terminal","payload":{"command":"ls -la"}}}}
Agent: Executes command
Agent → Server WS:   {"type":"tunnel_data","payload":{"channel_id":"...","data":{"type":"terminal_output","payload":{"stdout":"...\n","stderr":"","exit_code":0}}}}
Server → Client WS:  Forwards output to connected clients
```

### 3. Error Handling
```
Agent encounters error
Agent → Server WS:   {"type":"tunnel_error","payload":{"channel_id":"...","error":"Failed to execute: permission denied"}}
Server: Logs error, forwards to clients (Phase 2)
```

### 4. Close Tunnel
```
Client → Server API: POST /api/v1/tunnel/close {"session_id":"..."}
Server → Agent WS:   {"type":"tunnel_close","payload":{"session_id":"..."}}
Server: Updates database (status='closed', closed_at=NOW())
```

### 5. Agent Disconnect
```
Agent WebSocket closes
Server: Detects disconnect
Server: Calls close_agent_tunnel_sessions(agent_id)
Server: Sets all active sessions to 'closed'
Server: Logs count of sessions closed
```

---

## JSON Examples

### TunnelOpen (Server → Agent)
```json
{
  "type": "tunnel_open",
  "payload": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "tech_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  }
}
```

### TunnelReady (Agent → Server)
```json
{
  "type": "tunnel_ready",
  "payload": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
```

### TunnelData - Terminal Command (Server → Agent)
```json
{
  "type": "tunnel_data",
  "payload": {
    "channel_id": "terminal-1",
    "data": {
      "type": "terminal",
      "payload": {
        "command": "ls -la /home"
      }
    }
  }
}
```

### TunnelData - Terminal Output (Agent → Server)
```json
{
  "type": "tunnel_data",
  "payload": {
    "channel_id": "terminal-1",
    "data": {
      "type": "terminal_output",
      "payload": {
        "stdout": "total 8\ndrwxr-xr-x  2 user user 4096 Jan 01 12:00 .\ndrwxr-xr-x 20 root root 4096 Jan 01 12:00 ..\n",
        "stderr": "",
        "exit_code": 0
      }
    }
  }
}
```

### TunnelError (Agent → Server)
```json
{
  "type": "tunnel_error",
  "payload": {
    "channel_id": "terminal-1",
    "error": "Failed to execute command: No such file or directory"
  }
}
```

### TunnelClose (Server → Agent)
```json
{
  "type": "tunnel_close",
  "payload": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
```

---

## HTTP API Endpoints

### Open Tunnel
```http
POST /api/v1/tunnel/open
Authorization: Bearer <jwt_token>
Content-Type: application/json

{
  "agent_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

**Response:**
```json
{
  "session_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "active"
}
```

**Status Codes:**
- 200 OK - Tunnel opened successfully
- 400 Bad Request - Invalid agent_id format
- 404 Not Found - Agent not connected
- 409 Conflict - Active session already exists

---

### Close Tunnel
```http
POST /api/v1/tunnel/close
Authorization: Bearer <jwt_token>
Content-Type: application/json

{
  "session_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
```

**Response:**
```json
{
  "status": "closed"
}
```

**Status Codes:**
- 200 OK - Tunnel closed successfully
- 400 Bad Request - Invalid session_id format
- 403 Forbidden - Session not owned by user
- 404 Not Found - Session not found

---

### Get Tunnel Status
```http
GET /api/v1/tunnel/status/{session_id}
Authorization: Bearer <jwt_token>
```

**Response:**
```json
{
  "session_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "agent_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "active",
  "opened_at": "2026-04-14T10:30:00Z",
  "last_activity": "2026-04-14T10:31:45Z"
}
```

**Status Codes:**
- 200 OK - Status retrieved successfully
- 400 Bad Request - Invalid session_id format
- 403 Forbidden - Session not owned by user
- 404 Not Found - Session not found

---

## Database Schema

```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',
    CONSTRAINT unique_active_session UNIQUE (tech_id, agent_id, status)
        WHERE status = 'active'
);
```

**Indexes:**
- `idx_tech_sessions_tech` on `tech_id`
- `idx_tech_sessions_agent` on `agent_id`
- `idx_tech_sessions_status` on `status`

---

## Error Codes

### PostgreSQL Errors
- `23505` - Unique constraint violation (handled as 409 Conflict)

### HTTP Status Codes
- `400` - Bad Request (invalid UUID format, malformed JSON)
- `401` - Unauthorized (missing/invalid JWT token)
- `403` - Forbidden (session not owned by user)
- `404` - Not Found (agent offline, session doesn't exist)
- `409` - Conflict (active session already exists)
- `500` - Internal Server Error (database failure, unexpected error)

---

## Implementation Checklist

### Phase 1 (Complete)
- [x] Database schema (`tech_sessions` table)
- [x] Server message types (`TunnelOpen`, `TunnelClose`, `TunnelData`)
- [x] Agent message types (`TunnelReady`, `TunnelData`, `TunnelError`)
- [x] HTTP API endpoints (open, close, status)
- [x] WebSocket message handlers (all 3 agent messages)
- [x] Session ownership validation
- [x] Unique constraint handling (409 Conflict)
- [x] Agent disconnect cleanup
- [x] Foreign key constraints
- [x] Error logging and monitoring

### Phase 2 (Pending)
- [ ] Client WebSocket endpoint for output streaming
- [ ] Command execution endpoint (send Terminal commands)
- [ ] Output buffering/forwarding to clients
- [ ] Client connection tracking
- [ ] Real-time output streaming
- [ ] Command audit logging

---

**Last Updated:** 2026-04-14
