Filarr Mobile, Stand der Woche: eine ehrliche Synchronisierung

Diese Woche in der mobilen App von Filarr: fünf Sync-Korrekturen, zwei neue dauerhafte Fehlerzustände, der echte Speicherverbrauch und 3.326 grüne Tests.

Mathis Belouar-Pruvot

· Gründer von Filarr · · 7 Min. Lesezeit

Diese Woche in der mobilen App: Die Synchronisierung sagt die Wahrheit. Fünf Korrekturen, zwei neue dauerhafte Fehlerzustände, ein Startbildschirm, der endlich Ihren Speicherverbrauch anzeigt, und 3.326 grüne Tests. Die mobile App von Filarr erscheint in etwa ein bis zwei Monaten.

Die mobile App von Filarr (React Native) ist auf der Zielgeraden vor der Veröffentlichung. Bevor sie erscheint, muss die Synchronisierungsschicht ehrlich sein, nicht nur funktionieren, sondern ehrlich sein: Wenn etwas nicht stimmt, soll die App sagen, was sie weiß, nur das, was sie weiß, und aufhören, so zu tun, als würde sie Dinge reparieren, die sie nicht reparieren kann.

Das war das Thema dieser Woche.

Schluss mit dem ewigen Banner „sync interrupted“

Der sichtbarste Fehler auf dem Telefon war ein Banner, das nicht verschwinden wollte: „sync interrupted“ (Synchronisierung unterbrochen). Beim Durchsehen der Geräteprotokolle fand ich drei getrennte Ursachen, die sich in einer üblen Schleife gegenseitig nährten.

Phantomeinträge im Manifest. Eine Datei stand mit allen richtigen Metadaten im Synchronisierungsmanifest, aber ihr R2-Objekt war weg. 404, bestätigt. Der Synchronisierungszyklus behandelte diesen 404 als vorübergehenden Netzwerkfehler („fetch-failed“), also: Ich versuche es noch einmal. Aber ein 404 vom Server ist kein vorübergehender Aussetzer. Er ist eine Tatsache. Die Datei ist nicht da.

Die Korrektur führt einen neuen dauerhaften Fehlerzustand ein: missing-remote-object. Er greift nur, wenn das Fehlen bewiesen ist (der Server hat mit 404 geantwortet, nicht geschwiegen und nicht das Zeitlimit überschritten). Das Banner verschwindet. Die Datei bekommt eine ehrliche Zeile im Tresor. Keine Bereinigung: Den Eintrag zu entfernen wäre eine Behauptung, die andere Geräte lesen würden, und ich habe nichts zu behaupten, nur etwas festzustellen. Ich habe außerdem den Desktop-Code geprüft und bestätigt, dass ein Eintrag, dessen entferntes Objekt fehlt, zum Hochladen und nicht zum Löschen geht, sodass keine lokale Kopie an anderer Stelle gefährdet ist.

Veraltete Prüfsummen im Manifest. Eine weitere Ursache für das ewige Banner: eine abweichende Prüfsumme. Das Manifest enthielt eine Prüfsumme, die nicht mehr zum Objekt in R2 passte, ein Überbleibsel einer Migration, bei der Einträge mit der Prüfsumme des Chiffrats geschrieben wurden und das Objekt danach unter demselben deterministischen Schlüssel neu geschrieben wurde. Die Integritätsprüfung tat ihre Arbeit, aber die Abweichung war dauerhaft, nicht vorübergehend.

Die Korrektur weicht auf einen kryptografischen Beweis aus, statt abzulehnen. Die Bytes sind bereits heruntergeladen (kein zweiter Hin- und Rückweg nötig). Sie durchlaufen die Prüfung des AES-GCM-Tags unter den infrage kommenden Schlüsseln. Bestehen sie, wird der Eintrag repariert: Prüfsumme und Größe werden aktualisiert, per Compare-and-Swap, damit eine gleichzeitige Neuveröffentlichung das letzte Wort behält. Bestehen sie nicht, bleibt das alte Verhalten genau erhalten, und aus nicht authentifizierten Bytes wird nichts geschlossen. Die Prüfsumme im Manifest ist eine billige Vorprüfung, die wir selbst geschrieben haben. Der GCM-Tag ist ein kryptografischer Beweis für Herkunft und Integrität zugleich. Wenn sie sich widersprechen, vertrauen Sie dem Beweis. Wenn Sie genauer sehen wollen, wie sich die Verschlüsselungsebenen von Filarr in der Praxis stapeln, geht dieser Beitrag die gesamte Verteidigungsarchitektur durch.

Eine Verklemmung zwischen Phantomeinträgen und Synchronisierungsautorität. Die dritte Ursache war eine echte Verklemmung. Das Bereinigen von Phantomeinträgen verlangte Synchronisierungsautorität. Synchronisierungsautorität verlangte einen Zyklus ohne Beschädigung. Die einzige verbliebene „Beschädigung“ waren die Phantomeinträge selbst. Das Banner zeigte alle drei Gesichter: Beschädigung, unterbrochene Übertragung, nicht platzierbarer Eintrag. Keines verschwand.

Die Korrektur hat drei Teile. Ein neuer dauerhafter Fehlertyp, unplaceable-entry, deckt Einträge ab, die nur entfernt existieren und keinen lokalen Pfad haben. Das ist ein Schemafehler, keine Verletzung der Integrität. Das rote Beschädigungsbanner leuchtet nicht auf. Die Autorität wird wieder erreichbar. Die Migration veröffentlicht jetzt localPath für jeden Blob-Eintrag, sodass keine neuen nicht platzierbaren Einträge mehr entstehen. Und überflüssige Einträge können unter Autorität bereinigt werden, wenn im selben Manifest ein nachgewiesenes Duplikat existiert, mit einer besonderen Schutzregel: Zwei nicht platzierbare Zwillinge bereinigen sich nie gegenseitig.

Versionierung der Lesefähigkeit: Alte Urteile überleben den Leser nicht

Unabhängig von der Banner-Schleife: Vierzehn Dateien eines Desktop-Profils blieben auf dem Telefon als „unreadable“ (nicht lesbar) markiert, selbst nach einem Update, das genau die Lesefähigkeit hinzufügte, die sie brauchten. Kein neuer Versuch, kein erneuter Abruf. Der Zyklus spielte ein gespeichertes Urteil aus einer älteren Version der App ab.

Urteile über Nichtverfügbarkeit zwischenzuspeichern, ist im Prinzip vernünftig: Laden Sie keine Datei erneut herunter, die aus demselben Grund scheitern wird. Aber „Diese Version der mobilen App kann diese Datei nicht lesen“ ist ein Urteil, das ein bestimmter Leser gefällt hat. Es muss seine Version tragen.

Das gespeicherte Urteil ist jetzt mit READER_CAPABILITY_VERSION versehen. Ein Urteil aus einer früheren Version oder ein nacktes Urteil aus der Zeit vor der Versionierung gilt als veraltet und wird einmal neu versucht. Die Regel für das Hochzählen: jedes Mal, wenn die Lesefähigkeit wächst (eine neue Containerfamilie, ein neues Transportformat, ein neuer Schlüsselkandidat). Nicht bei Fehlerkorrekturen, die nichts daran ändern, was gelesen werden kann. Jedes Hochzählen kostet einen Download pro zuvor nicht verfügbarem Eintrag, ist also nicht kostenlos, aber ehrlich.

Startbildschirm: Ihr Speicherkontingent wird jetzt wirklich abgerufen

Der Block zum Speicherverbrauch auf dem Startbildschirm zeigte bei Cloud-Konten immer „usage unavailable“ (Verbrauch nicht verfügbar). Jedes einzelne Mal.

Die API war da. Der Worker liefert storageUsed und storageLimit bei GET /billing/status. Der Client hatte ein typisiertes getBillingStatus. Die Anzeigelogik, resolveStorageBlock, wusste, wie man die Anzeige und die Warnschwelle berechnet, mit Tests. Es fehlte nur der Aufruf. HomeScreen rief resolveStorageBlock({ accountMode, tier }) auf, ohne je die Bytes zu übergeben. Die Beschriftung sagte die Wahrheit, unbeabsichtigt und auf unbestimmte Zeit.

Das Kontingent wird jetzt beim Einbinden abgerufen, beim Wechsel des Kontomodus und am Ende jedes vollständigen Synchronisierungszyklus. Es ändert sich, wenn Sie hochladen, eine beim Einbinden eingefrorene Zahl würde also vor Ihren Augen veralten. Nach bestem Bemühen und nie blockierend: Offline oder bei schweigendem Server fällt der Block ehrlich auf „unavailable“ zurück. Eine drei Tage alte Zahl, als aktuell präsentiert, wäre schlimmer als gar keine Zahl. Kein Aufruf bei lokalen Konten: Dort wird der Block nicht angezeigt.

Darüber liegt ein Aktualitätsfenster von zwei Minuten. Gemessen auf dem Gerät vor der Korrektur: sieben Aufrufe von /billing/status in zwanzig Sekunden, weil der Startbildschirm bei jeder Rückkehr zum Tab neu eingebunden wird. Die Geste, die das Kontingent verändert (zum Aktualisieren ziehen), erzwingt jetzt ein sofortiges erneutes Lesen. Zwei Minuten zwischen automatischen Abrufen: kurz genug, dass eine Rückkehr zum Startbildschirm nach einem Import die richtige Zahl zeigt, lang genug, dass die normale Navigation nichts kostet. Der Zeitstempel wird nur bei Erfolg gesetzt, sodass ein kurzer Ausfall den Block nicht für das ganze Fenster zu „unavailable“ verurteilt.

Zum Hintergrund, wie die optionale Cloud-Synchronisierung Zero-Knowledge bleibt, während all diese Synchronisierungslogik läuft: Dieser Beitrag beschreibt, was tatsächlich zum Server reist, nämlich verschlüsselte Datenblöcke, die für uns undurchsichtig sind.

3.326 Tests, alle grün

Jede Korrektur dieser Woche wurde mit Tests ausgeliefert. Die Testsuite für Synchronisierungsfehler deckt die Erkennung von missing-remote-object für beide Formen einer axios-Antwort ab (verpackt und roh), die Versionierung der Lesefähigkeit und die Übernahme alter Cache-Einträge, das Ausweichen bei veralteter Prüfsumme mit GCM als Rückfallebene, die Lösung der Verklemmung einschließlich der Schutzregel für nicht platzierbare Zwillinge und das Verhalten des Ordnerzyklus bei 404. Die Übersetzungsparität liegt bei 1.477/1.477 Zeichenketten.

Eine erwähnenswerte Ergänzung: Beim Start wird jetzt eine Build-Markierung ins Diagnoseprotokoll geschrieben. Das klingt trivial. Ist es nicht. Eine verlorene Diagnosesitzung hat einmal echte Zeit bei der Fehlersuche gekostet, weil ich nicht bestätigen konnte, dass auf dem Gerät der Code lief, von dem ich ausging. Die Markierung schließt diese Lücke.

Was als Nächstes kommt

Die Synchronisierungsschicht ist jetzt an einem Punkt, an dem ich sie zuverlässig nennen würde. Als Nächstes auf dem Telefon:

  • Feinschliff der Oberfläche: Die Tresorliste, der Editor und der Einrichtungsablauf haben von der schnellen Iteration noch raue Kanten
  • Einrichtungsbildschirme: Die Bildschirme für Schlüsselableitung und Wiederherstellungsphrase brauchen einen letzten Designdurchgang, bevor sie neue Nutzer zu sehen bekommen (das Verschlüsselungsmodell aus KEK und FEK, das sie einrichten, ist dasselbe, das heute auf dem Desktop läuft)
  • End-to-End-Tests auf einem echten Gerät über den gesamten Synchronisierungszyklus: Upload vom Desktop, Lesen auf dem Telefon, Bearbeiten auf dem Telefon, Konfliktlösung zurück auf dem Desktop
  • Ein kleiner Beta-Build für frühe Testerinnen und Tester

Wenn Sie den Aufbau verfolgen oder früh Zugang bekommen möchten: Der Beitrag zum Meilenstein von 1.000 Nutzern zeigt das ganze Bild, wo wir stehen und was kommt, nachdem die mobile App erschienen ist. Der Quellcode des Desktop-Clients ist bereits unter der BSL 1.1 öffentlich, und die mobile App übernimmt dieselbe Verschlüsselungsarchitektur.

Noch etwa ein bis zwei Monate. Mit öffentlichem Aufbau auf dem ganzen Weg.