Skip to content

files_routes

files_routes

File-transfer routes: offer, send, verify, publish.

Spatial Mesh, phase 3 — 25 August 2026. Four routes, one session, and a credential that is NOT the API key: the sending device does not have it.

  • POST /v1/mesh/files/offer — an Ed25519-signed envelope checked by the same verify_payload as presence beacons: seven checks, including revocation. Pairing is durable consent: a TRUSTED peer immediately receives a session without a second click for every file.
  • POST /v1/mesh/files/{id}/chunk — one encrypted chunk authorized by the session token. The signature guards the door; the token guards the hall.
  • POST /v1/mesh/files/{id}/finish — verifies the digest and exposes the file atomically.

The real ceiling on these routes is not a rate but a VOLUME, enforced here: bytes received per session and the number of simultaneous sessions. A request-rate limiter says nothing about body size.

Classes

Offre

Bases: BaseModel

A signed file announcement from an already trusted peer.

Functions:

reinitialiser_pour_tests

reinitialiser_pour_tests() -> None

Vider les sessions — les tests ne doivent pas se contaminer.

Source code in src/diapason/mesh/files_routes.py
def reinitialiser_pour_tests() -> None:
    """Vider les sessions — les tests ne doivent pas se contaminer."""
    for session in list(_sessions.values()):
        try:
            session.reception.abandonner()
        except Exception:  # noqa: BLE001
            pass
    _sessions.clear()

offrir

offrir(body: Offre) -> dict[str, Any]

Verify the offer and open a session for that already trusted peer.

Source code in src/diapason/mesh/files_routes.py
@router.post("/offer")
def offrir(body: Offre) -> dict[str, Any]:
    """Verify the offer and open a session for that already trusted peer."""
    from diapason.mesh.identity import device_identity, owner_id
    from diapason.mesh.registry import TRUST_TRUSTED, DeviceRegistry
    from diapason.mesh.signed import SignedRejected, verify_payload
    from diapason.mesh.transfert import (
        Manifeste,
        RefusDeTransfert,
        deja_present,
        verifier_le_manifeste,
    )

    _purger()
    if len(_sessions) >= _SESSIONS_MAX:
        raise HTTPException(
            status_code=429,
            detail="Trop de transferts en cours. Réessaie dans un moment.",
        )

    registry = DeviceRegistry()
    brut = body.model_dump()
    try:
        device_id = verify_payload(
            brut,
            fields=_CHAMPS_SIGNES,
            version=OFFER_VERSION,
            registry=registry,
            local_owner_id=owner_id(),
            local_device_id=device_identity().device_id,
            now_ms=int(time.time() * 1000),
            subject="offre de fichier",
        )
    except SignedRejected as exc:
        raise HTTPException(status_code=403, detail=str(exc)) from exc

    # `verify_payload` already accepts only a TRUSTED peer key. Reading the
    # row again closes the small race where the device is revoked immediately
    # after signature verification, before even deduplication can reveal a
    # file already present on this machine.
    appareil = registry.find(device_id)
    if appareil is None or appareil.get("trustLevel") != TRUST_TRUSTED:
        raise HTTPException(
            status_code=403,
            detail="Cet appareil n'est plus autorisé à envoyer des fichiers.",
        )

    try:
        manifeste = Manifeste.from_dict(body.manifest or {})
    except (TypeError, ValueError, OverflowError) as exc:
        raise HTTPException(
            status_code=400, detail="Manifeste de fichier illisible."
        ) from exc
    dossier = dossier_de_reception()

    # Vérifier le manifeste ne crée ni dossier ni fichier. Avant cette
    # séparation, `ouvrir_reception` faisait mkdir puis répondait ACCEPTED :
    # la décision était prise et le disque touché avant le premier regard.
    try:
        verifier_le_manifeste(manifeste, taille_max=_taille_max())
    except RefusDeTransfert as exc:
        raise HTTPException(status_code=413, detail=str(exc)) from exc

    # Déduplication AVANT tout octet (§45) : si le contenu est déjà là,
    # l'annoncer coûte une réponse, le transférer coûterait le fichier.
    existant = deja_present(manifeste, dossier)
    if existant is not None:
        return _repondre(
            {
                "status": "ALREADY_PRESENT",
                "path": str(existant),
                "userSafeMessage": (
                    f{existant.name} » est déjà là — rien à envoyer."
                ),
            }
        )

    # A paired peer no longer has to ask (28 August 2026), and that is
    # deliberate. But "no longer asks" must not mean "without limit": these
    # two guards are the whole of what now stands between a paired device
    # gone hostile and a full disk. Checked AFTER deduplication on purpose —
    # a file already present costs no bytes, and refusing it for lack of room
    # would be refusing something we were not going to store.
    refus = _refus_de_volume(manifeste.taille)
    if refus is not None:
        logger.warning(
            "file transfer refused for lack of room: from=%s size=%d",
            device_id,
            manifeste.taille,
        )
        raise HTTPException(status_code=507, detail=refus)

    reponse = _open_session(
        device_id=device_id,
        device_name=_display_name(appareil.get("name")),
        manifeste=manifeste,
        sender_public_key=body.ephemeralPublicKey,
    )
    logger.info(
        "file transfer accepted automatically: from=%s size=%d",
        device_id,
        manifeste.taille,
    )
    return _repondre(reponse)

morceau async

morceau(
    session_id: str, index: int, request: Request
) -> dict[str, Any]

Recevoir un morceau chiffré, à sa place exacte.

Source code in src/diapason/mesh/files_routes.py
@router.post("/{session_id}/chunk")
async def morceau(session_id: str, index: int, request: Request) -> dict[str, Any]:
    """Recevoir un morceau chiffré, à sa place exacte."""
    from diapason.mesh.coffre import desceller
    from diapason.mesh.transfert import RefusDeTransfert

    session = _session_autorisee(session_id, request)
    scelle = await request.body()
    # LE plafond qui compte : un limiteur de requêtes ne dit rien de la
    # taille d'un corps. On borne ce qu'une session a le droit de peser.
    plafond = session.reception.manifeste.taille + (1024 * 64)
    if session.octets_recus + len(scelle) > plafond:
        session.reception.abandonner()
        _sessions.pop(session_id, None)
        raise HTTPException(
            status_code=413,
            detail="Cette session a envoyé plus que ce qu'elle avait annoncé.",
        )
    try:
        clair = desceller(session.cle, index, scelle)
        session.reception.ecrire(index, clair)
    except (ValueError, RefusDeTransfert) as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    session.octets_recus += len(scelle)
    return {
        "received": index,
        "missing": session.reception.manquants,
        "complete": session.reception.complet,
    }

finir

finir(session_id: str, request: Request) -> dict[str, Any]

Vérifier l'empreinte, puis rendre le fichier visible d'un seul coup.

Source code in src/diapason/mesh/files_routes.py
@router.post("/{session_id}/finish")
def finir(session_id: str, request: Request) -> dict[str, Any]:
    """Vérifier l'empreinte, puis rendre le fichier visible d'un seul coup."""
    from diapason.mesh.transfert import RefusDeTransfert

    session = _session_autorisee(session_id, request)
    try:
        cible = session.reception.finaliser()
    except RefusDeTransfert as exc:
        _sessions.pop(session_id, None)
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    _sessions.pop(session_id, None)
    taille = cible.stat().st_size
    # Recorded BEFORE the shell is told: the journal is what makes the
    # arrival true, the animation is only what makes it visible.
    _record_arrival(session, cible, taille)
    _publish_received_file(session, cible, taille)
    logger.info("file transfer complete: %s", cible.name)
    return _repondre(
        {
            "status": "COMPLETE",
            "sessionId": session_id,
            "path": str(cible),
            "name": cible.name,
            "bytes": taille,
            "userSafeMessage": f{cible.name} » est arrivé.",
        }
    )

etat

etat(session_id: str, request: Request) -> dict[str, Any]

Ce qui manque encore — c'est là que vit la reprise.

Source code in src/diapason/mesh/files_routes.py
@router.post("/{session_id}/status")
def etat(session_id: str, request: Request) -> dict[str, Any]:
    """Ce qui manque encore — c'est là que vit la reprise."""
    session = _session_autorisee(session_id, request)
    return {
        "sessionId": session_id,
        "missing": session.reception.manquants,
        "complete": session.reception.complet,
    }