solab — Laboratorio 2 de Sockets 1.0.0

Protocolo del laboratorio 2 de Sistemas Operativos (CIIC4050).

Cada pareja de estudiantes escribe un cliente en Python que corre en una Raspberry Pi y habla con este servidor por un socket TCP crudo.

Antes de programar, tres cosas

1. TCP no tiene mensajes, tiene bytes. Lo que envías con un sendall() puede llegar partido en varios recv(), y varios mensajes seguidos pueden llegar pegados en uno solo. Por eso cada mensaje de este protocolo es un objeto JSON en una línea, terminado en \n, y hay que leer hasta encontrar el salto de línea. Un cliente que haga json.loads(sock.recv(4096)) funciona en tus pruebas y falla en el laboratorio.

2. Identidad y autenticación no son lo mismo. Tu Raspberry se identifica con una huella de hardware, que viaja en claro y cualquiera podría copiar. Se autentica demostrando que conoce un secreto, sin enviarlo: el servidor manda un nonce y tú devuelves HMAC-SHA256(device_secret, nonce).

3. Son parejas. Cada Raspberry admite como máximo 2 estudiantes activos, y ambos deben ser de la misma sección.

El flujo completo

conectar TCP :9000
    <- hello              (trae el nonce y los límites del servidor)
device.register           (una sola vez por Raspberry, con el código pegado a ella)
    -> device.registered  (device_id + device_secret: GUÁRDALOS)
session.open              (HMAC del nonce)
    -> session.ok         (session_token)
student.enroll  x2        (tú y tu pareja)
data.push       xN        (los datos del laboratorio)
session.close

El sobre

Todo mensaje, en las dos direcciones, tiene la misma forma:

{"v": 1, "id": "c7", "action": "student.enroll", "token": "…", "payload": {}}
campo quién para qué
v los dos versión del protocolo; hoy 1
id cliente id de correlación; el servidor lo devuelve en su respuesta
action los dos qué mensaje es
token cliente session_token; obligatorio salvo en device.register, session.open y session.resume
payload los dos el cuerpo, distinto para cada acción

El campo id existe porque sobre un flujo puedes enviar tres mensajes sin esperar respuesta, y necesitas saber cuál respuesta corresponde a cuál.

Servers

  • tcp://solab.ivanvasquez.tech:9500/tcplaboratorio

    El socket crudo contra el que trabajas. Un objeto JSON por línea, terminado en \n.

    Va sin TLS, a propósito. El HMAC demuestra quién eres, pero no cifra nada: cualquiera en la misma red puede leer lo que envías. Esa distinción entre autenticación y confidencialidad es parte del laboratorio.

    El puerto es el 9500 y no el 9000 porque en ese servidor el 9000 ya está ocupado por otro servicio.

    Puedes ver el saludo sin escribir una línea de código:

    nc solab.ivanvasquez.tech 9500
    
  • tcp://localhost:9000/tcplocal

    Para quien levante el servidor en su propia máquina siguiendo el README del repositorio. Mismo protocolo, otro host y puerto.

  • wss://solab.ivanvasquez.tech/ws/dashboardwsstablero

    WebSocket de solo lectura que alimenta el tablero en vivo. Requiere el token de administración del docente: los estudiantes no lo usan.

Operations

  • RECEIVE /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    El servidor saluda en cuanto aceptas la conexión, sin que pidas nada.

    No envías nada para recibirlo: llega solo. Trae el nonce que necesitas para session.open, y los límites del servidor para que no los adivines.

    El nonce es de esta conexión. Si te reconectas, el nonce es otro y la prueba anterior ya no sirve. Eso es lo que impide que alguien capture tu prueba y la reutilice.

    Operation IDrecibirSaludo

    Available only on servers:

    Accepts the following message:

    hello

    Saludo del servidor, con el nonce del reto.

    Message IDhello
    allOf

    Examples

  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Canjea el código pegado a tu Raspberry por unas credenciales permanentes.

    El enrollment_code está impreso en una etiqueta pegada a tu Raspberry y es de un solo uso. Sin ese papel no puedes registrar nada: es lo que impide que alguien registre "una Raspberry" desde su portátil.

    El device_secret que recibes se muestra una sola vez. Guárdalo en /etc/solab/device.json con permisos 600 antes de hacer nada más. Si lo pierdes, tienes que pedirle al docente un código nuevo.

    La huella de hardware que envías no te autentica: viaja en claro y es falsificable. Le sirve al servidor para detectar que la misma Raspberry se registró dos veces, o que una imagen se reinstaló.

    Operation IDregistrarDispositivo

    Available only on servers:

    Accepts the following message:

    device.register

    Canjea el enrollment code por credenciales.

    Message IDdeviceRegister
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Prueba que conoces el device_secret sin enviarlo.

    Calcula HMAC-SHA256(device_secret, nonce) en hexadecimal minúscula y envíalo como proof. En Python, con la biblioteca estándar:

    import hmac, hashlib
    proof = hmac.new(secret.encode(), nonce.encode(), hashlib.sha256).hexdigest()
    

    El secret se usa tal como viene en tu device.json, como cadena: no hay que pasarlo por bytes.fromhex().

    A cambio recibes un session_token, que va en el campo token del sobre de todos los mensajes siguientes.

    Operation IDabrirSesion

    Available only on servers:

    Accepts the following message:

    session.open

    Prueba HMAC del nonce.

    Message IDsessionOpen
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Vuelve a usar un token vigente sin repetir el HMAC.

    Las conexiones se caen. session.resume te devuelve a donde estabas sin tener que releer el secreto del disco. El token sigue siendo el mismo: reanudar no lo rota.

    Si el token venció (TOKEN_EXPIRED) o fue revocado, toca session.open otra vez.

    Operation IDreanudarSesion

    Available only on servers:

    Accepts the following message:

    session.resume

    Reanuda con un token vigente.

    Message IDsessionResume
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Matricula un estudiante en esta Raspberry.

    Cada integrante de la pareja se matricula por separado. Se aplican tres reglas, y cada una tiene su código de error:

    regla si la rompes
    máximo 2 estudiantes activos por Raspberry DEVICE_FULL
    un estudiante, una sola pareja activa STUDENT_ALREADY_ENROLLED
    los 2 de una Raspberry, misma sección SECTION_MISMATCH

    Las secciones válidas son 070, 071, 100 y 101; cualquier otra da INVALID_SECTION.

    Repetir la misma matrícula no es un error: si reintentas tras un timeout, recibes el estado actual igual que la primera vez.

    Operation IDmatricularEstudiante

    Available only on servers:

    Accepts the following message:

    student.enroll

    Matricula un estudiante en esta Raspberry.

    Message IDstudentEnroll
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Quita a un estudiante de esta Raspberry.

    Solo desde la Raspberry donde estás matriculado. Si te equivocaste de Pi, libérate desde aquella, no desde esta.

    Operation IDliberarEstudiante

    Available only on servers:

    Accepts the following message:

    student.leave

    Libera el puesto de un estudiante.

    Message IDstudentLeave
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Quién soy, quién está matriculado, cuántos puestos quedan.

    La acción que más vas a usar mientras depuras. No lleva payload.

    Operation IDconsultarEstado

    Available only on servers:

    Accepts the following message:

    device.status

    Estado de la Raspberry. Sin payload.

    Message IDdeviceStatus
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Envía una medición al servidor.

    kind nombra el tipo de dato y data es un objeto JSON libre: el servidor no valida su contenido.

    seq es tu contador. Reenviar un seq que ya enviaste en la misma sesión no duplica ni falla: el ack te responde stored: false. Eso hace que reintentar tras un timeout sea seguro.

    Hay un límite de mensajes por minuto; si lo pasas recibes RATE_LIMITED. No es un castigo: es para que un bucle sin sleep no tumbe el servidor del resto del laboratorio.

    Operation IDenviarDatos

    Available only on servers:

    Accepts the following message:

    data.push

    Envía una medición.

    Message IDdataPush
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Evita que el servidor cierre por inactividad.

    El servidor cierra las conexiones sin tráfico (mira heartbeat_seconds y el timeout en el saludo). Si tu cliente va a estar callado un rato, manda ping.

    Operation IDlatido

    Available only on servers:

    Accepts the following message:

    ping

    Latido. Sin payload.

    Message IDping
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • REQUEST /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Revoca el token antes de desconectarte.

    Opcional, pero es buena costumbre y deja el tablero del docente limpio.

    Operation IDcerrarSesion

    Available only on servers:

    Accepts the following message:

    session.close

    Revoca la sesión. Sin payload.

    Message IDsessionClose
    allOf

    Examples

    REPLY INFORMATION

    REPLY CHANNEL INFORMATION

    Reply will be provided via this designated address: /
  • RECEIVE /

    Todo el protocolo va por la misma conexión TCP. No hay rutas ni endpoints: lo que distingue un mensaje de otro es el campo action.

    Respuesta de error, con un código estable contra el que puedes programar.

    Cualquier acción puede responder error en lugar de su respuesta normal. El id es el de tu mensaje, así que sabes cuál falló.

    Programa contra code, no contra detail: el código es contrato y no cambia; el detalle está escrito para que lo leas tú y puede mejorar.

    Operation IDrecibirError

    Available only on servers:

    Accepts the following message:

    error

    Error con código estable.

    Message IDerror
    allOf

    Examples

Messages

  • #1hello

    Saludo del servidor, con el nonce del reto.

    Message IDhello
    allOf
  • #2device.register

    Canjea el enrollment code por credenciales.

    Message IDdeviceRegister
    allOf
  • #3device.registered

    Credenciales permanentes. El secreto se muestra una sola vez.

    Message IDdeviceRegistered
    allOf
  • #4session.open

    Prueba HMAC del nonce.

    Message IDsessionOpen
    allOf
  • #5session.resume

    Reanuda con un token vigente.

    Message IDsessionResume
    allOf
  • #6session.ok

    Sesión abierta. Trae el token y el estado de la Raspberry.

    Message IDsessionOk
    allOf
  • #7session.close

    Revoca la sesión. Sin payload.

    Message IDsessionClose
    allOf
  • #8session.closed

    Confirmación del cierre.

    Message IDsessionClosed
    allOf
  • #9student.enroll

    Matricula un estudiante en esta Raspberry.

    Message IDstudentEnroll
    allOf
  • #10student.leave

    Libera el puesto de un estudiante.

    Message IDstudentLeave
    allOf
  • #11device.status

    Estado de la Raspberry. Sin payload.

    Message IDdeviceStatus
    allOf
  • #12device.status.ok / student.enrolled / student.left

    Estado completo de la Raspberry. Es la respuesta de device.status y también la de student.enroll (como student.enrolled) y student.leave (como student.left).

    Message IDdeviceStatusOk
    allOf
  • #13data.push

    Envía una medición.

    Message IDdataPush
    allOf
  • #14data.ack

    Confirmación. stored=false significa que ese seq ya estaba.

    Message IDdataAck
    allOf
  • #15ping

    Latido. Sin payload.

    Message IDping
    allOf
  • #16pong

    Respuesta al latido, con la hora del servidor.

    Message IDpong
    allOf
  • #17error

    Error con código estable.

    Message IDerror
    allOf

Schemas

  • object
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • allOf
  • object
  • object