# `puente-agent` — host

El binario que corre en la máquina que se comparte. Vive prendido 24/7 con acceso
desatendido, así que en reposo no hace prácticamente nada.

## Estado

**Funciona de punta a punta**, verificado contra un servidor real por internet:

- ✅ Identidad Ed25519 generada en el primer arranque y persistida con permisos `0600`
- ✅ Registro autenticado: firma el nonce del `server_hello` y recibe su device ID
- ✅ Keepalive y reconexión con retroceso exponencial topeado
- ✅ Máquina de estados de sesión, con la invariante de "no se captura sin autenticar"
- ✅ Bloqueo tras 3 intentos, que sobrevive a la reconexión
- ✅ **WebRTC**: los tres DataChannels, trickle ICE, negociación completa
- ✅ **Handshake E2EE** atado al fingerprint DTLS del SDP
- ✅ **Verificación de credencial** sobre el canal cifrado, con los tres modos
- ✅ **Captura**: DXGI en Windows, Core Graphics en macOS, varios monitores a la vez
- ✅ **Encoding H.264** por software (openh264), con el cuadro escalado al tope del encoder
- ✅ **Inyección de input**: `SendInput` con scancodes en Windows, CGEvent en macOS
- ✅ **Terminal remota** con PTY real y registro de comandos, que es el modo recuperación
- ✅ **Portapapeles** y **transferencia de archivos** en los dos sentidos
- ✅ **Instalación como servicio**, con auto-inicio y reinicio ante fallo
- ❌ Encoder por hardware (NVENC / QuickSync / AMF)
- ❌ Linux (PipeWire): fuera de alcance, producción es Windows

> **Compila para Windows y todavía no corrió en Windows.** Es lo que más falta. El `.exe` y
> el instalador salen del CI en cada versión; lo que no hay es una máquina Windows donde se
> haya instalado y usado.

## Probarlo

```bash
# En una terminal, el signaling:
cd ../../server && go run ./cmd/signal --addr :18080 --db /tmp/puente.db

# En otra, el agente:
cargo run -p puente-agent -- --signal ws://127.0.0.1:18080/ws --name "PC Oficina"
```

Sale algo así:

```
  Fingerprint de este equipo:
  83:ED:B1:7B:C8:AB:9F:15:27:31:75:92:EE:4C:D0:81

  Tu ID de equipo:  195 980 682
```

Y en `curl localhost:18080/metrics` se ve `puente_registrations_total 1`.

```bash
cargo test -p puente-agent      # 163 tests
```

## La invariante que sostiene todo

El host es el que **hace cumplir** la seguridad de la sesión. La verificación de fingerprint
que muestra el visor es una cortesía con el usuario: un visor modificado no está obligado a
mostrar nada.

Por eso la máquina de estados
([`session/state.rs`](src/session/state.rs)) existe. La regla es:

> No se captura ni se transmite nada hasta que la credencial esté verificada.

`Session::puede_capturar()` devuelve `true` **sólo** en `Streaming`, que es el estado al que
únicamente se llega pasando por handshake E2EE → confirmación de clave → credencial. Hay un
test que recorre el camino completo comprobando que en cada estado anterior la respuesta
sigue siendo `false` — incluido `Authenticating`, donde el canal ya está cifrado pero nadie
probó todavía conocer la contraseña.

El bucle de captura pregunta eso antes de tomar un solo cuadro. No es un `if` que alguien
pueda mover de lugar sin darse cuenta.

### Otras dos que es fácil romper

**El contador de intentos vive fuera de la sesión** ([`session/mod.rs`](src/session/mod.rs)).
Si viviera adentro, bastaría con reconectar para tener tres intentos nuevos y el bloqueo no
serviría para nada. Hay un test que se llama exactamente así.

**Los permisos los aplica el host, mensaje por mensaje.** Si `control` es falso, los eventos
de input que lleguen se descartan; no se confía en que el visor no los mande.

## Consumo en reposo

El objetivo es ~10 MB de RAM y ~0 % de CPU cuando no hay nadie conectado. Lo que corre en
ese estado es una sola tarea: un socket, un timer de keepalive y una cola de 32 mensajes.
Sin buffers grandes, sin pools, sin nada que se pague estando quieto.

Las decisiones que lo sostienen:

- `tokio` con las features mínimas, sin `full`
- La cola de envío es chica a propósito: si se llena es que algo dejó de drenar, y crecer
  sin límite sólo cambia un fallo rápido por un consumo que crece
- Captura y encoding sólo existen dentro de una sesión

Falta medirlo en Windows con el binario de release, que es el escenario que importa.

## La clave privada, y su límite

El agente corre **desatendido**: arranca con el sistema, sin nadie que desbloquee un
llavero. Se guarda con permisos `0600` en el directorio de datos de la aplicación (en
Windows correspondería además DPAPI en ámbito de máquina, pendiente).

**Comprometer root o SYSTEM es comprometer la identidad del dispositivo.** No hay forma de
evitarlo en un servicio desatendido: cualquier cosa que el servicio pueda leer sin
intervención humana, también la puede leer quien tenga ese acceso.

Lo que sí protege el sistema es que esa clave sola no alcanza: hace falta además la
credencial de acceso, que no se guarda en claro (sólo el verificador Argon2id en formato
PHC).

## Lo que sigue

En orden de lo que desbloquea más:

1. **Instalarlo en una máquina Windows** y usarlo. Todo lo de abajo es trabajo conocido;
   esto es lo único que puede tirar abajo un supuesto grande.
2. **Firma de código.** Sin firmar, SmartScreen bloquea el instalador y la gente no lo
   instala. Es lo único de la lista que cuesta plata.
3. **Encoder por hardware** (NVENC / QuickSync / AMF). Hoy va todo por software, que en una
   máquina vieja se nota.
4. **Auto-actualización.** Un equipo desatendido que hay que actualizar a mano se queda
   viejo.
5. **`Ctrl+Alt+Del`** en Windows: necesita SAS y que el servicio corra en la sesión correcta.

## Contrato

Los mensajes están en [`/protocol/PROTOCOL.md`](../../protocol/PROTOCOL.md), congelados en
v1. Leer §1 (modelo de amenaza), §7 (el orden real de una conexión) y §9 (lo que el host
tiene que hacer cumplir). Si hace falta un cambio, va a
[`NEEDS-PROTOCOL-CHANGE.md`](NEEDS-PROTOCOL-CHANGE.md).
