Maschinelle Übersetzung, nicht von einem Muttersprachler geprüft. Englisch ist die maßgebliche Fassung.

Empfängervertrauen und SeefahrtAuf GitHub bearbeitenQuelle: docs/OSNMA.md

Galileo-OSNMA-Verifizierer auf Empfängerseite

Ein Verifizierer auf Empfängerseite für die Galileo Open Service Navigation Message Authentication (OSNMA), in reinem Rust. Er liest I/NAV-Seiten und berichtet je Satellit und je Epoche, ob die Navigationsdaten kryptographisch authentifiziert sind und warum eine Prüfung fehlgeschlagen ist.

Hinweis (beratend). Dies ist eine Analyse- und Überwachungsausgabe und keine typzugelassene Navigationsausrüstung. Der hier gemeldete Authentifizierungsstatus ist kein Navigationsintegritätsdienst und darf nicht die alleinige Grundlage einer Navigations- oder Sicherheitsentscheidung sein.

HINWEIS: geistiges Eigentum an der Galileo-OSNMA-Schnittstelle#

Kshana gewährt keine Rechte am geistigen Eigentum der Europäischen Union am Galileo OSNMA Signal-in-Space Interface Control Document (den „ICD-IPRs“). Das ICD trägt seine eigene Genehmigung (sein Anhang E), die nicht ausschließlich und gebührenfrei ist und eigene Bedingungen, einen eigenen Umfang und eigene Grenzen hat. Wer auf diesem Modul beruhende Software nutzt, baut oder weitergibt, stützt sich unmittelbar auf diese Genehmigung und muss ihre Bedingungen selbst lesen und annehmen. Die Lizenz von Kshana (AGPL-3.0-only oder die kommerzielle Lizenz) deckt nur den eigenen Code von Kshana ab.

Dieses Modul implementiert die Algorithmen, die die unten genannten Dokumente beschreiben. Der Text des ICD wird nicht wiedergegeben; Zahlenwerte (Feldlayouts, Aufzählungen, die MAC-Nachschlagetabelle) sind in der eigenen Notation von Kshana neu ausgedrückt, mit Quellenangabe im Code. Das ICD und die Receiver Guidelines sind © Europäische Union; die Quelle wird hier genannt. Kshana wird nicht von der Europäischen Union, der Europäischen Kommission, der EUSPA oder einer anderen Stelle entwickelt, genutzt, genehmigt oder unterstützt.

Referenzdokumente#

Veröffentlicht vom European GNSS Service Centre.

Dokument Version Datum
Galileo OSNMA Signal-in-Space ICD („SIS ICD“) Ausgabe 1,1 Oktober 2023
Galileo OSNMA Receiver Guidelines Ausgabe 1,3 Januar 2024

Abschnitts- und Tabellennummern, die im Code und hier genannt werden, beziehen sich auf diese Fassungen.

Was verifiziert wird#

Die Vertrauenskette, von der Wurzel bis zu den Daten:

  1. Öffentlicher Schlüssel (ICD 3.2.2, 6,2). Ein DSM-PKR wird aus seinen Blöcken zusammengesetzt, und sein Merkle-Pfad wird bis zu einer vertrauenswürdigen Merkle-Wurzel (SHA-256) durchlaufen.
  2. KROOT (ICD 3.2.3, 6,3). Ein DSM-KROOT wird zusammengesetzt; seine ECDSA-Signatur (P-256/SHA-256 oder P-521/SHA-512) wird gegen den von ihm benannten öffentlichen Schlüssel geprüft, über den NMA-Header und die Kettenparameter.
  3. TESLA-Schlüssel (ICD 5,5, 6,4). Jeder Schlüssel in einer MACK-Nachricht wird mit der Hashfunktion der Kette bis zum KROOT oder zu einem früheren verifizierten Schlüssel zurückverfolgt.
  4. MACSEQ (ICD 4.1.2, 6,6). Die flexiblen Tag-Info-Felder werden mit dem Schlüssel, der diese MACK öffnet, gegen die MACSEQ geprüft.
  5. Tags (ICD 4,2, 6,7). Jedes Tag wird über die Satellitenkennungen, die Subframe-Zeit, die Tag-Position, den NMA-Status und die von ihm abgedeckten Navigationsdaten neu berechnet (HMAC-SHA-256 oder CMAC-AES, auf die Tag-Länge der Kette gekürzt) und verglichen. ADKD 0 (Ephemeride, Uhr und Status), ADKD 4 (Zeitgebungsparameter) und ADKD 12 (langsames MAC, Schlüssel zehn Subframes später veröffentlicht) werden behandelt, mit Kreuzauthentifizierung von Satelliten, die selbst kein OSNMA tragen, und Dummy-Tags (COP 0).

Ein Tag, das einen reservierten Wert nennt oder dessen ADKD nicht zum Platz der MAC-Nachschlagetabelle passt, wird beiseitegelegt (die Receiver Guidelines sehen vor, es zu verwerfen) und als discarded gemeldet, nicht als Fehlschlag.

Seiten werden vor allem anderen gegen ihre CRC (die Galileo-CRC-24Q) geprüft; eine Seite, die durchfällt, wird verworfen und gemeldet (bad_crc).

Statuswerte#

Je Tag: authenticated, failed mit einem Grund (tag_mismatch, macseq_mismatch, key_chain_mismatch, key_conflict), pending mit einem Grund (no_chain, awaiting_key, no_nav_data, unknown_maclt, unsupported_function, service_not_usable, chain_mismatch) oder discarded. Ein Dummy-Tag (COP 0) erscheint auf Tag-Ebene als authenticated, da es den Schlüssel belegt, aber es deckt keine Navigationsdaten ab und zählt nie für einen Satelliten.

Je Satellit, in der Form, die der Empfänger-Vertrauensmonitor verarbeitet:

  • Authenticated nur, solange die neuesten verifizierten Ephemeriden- und Uhrendaten (ADKD 0 oder
    1. dieses Satelliten aktuell sind (standardmäßig innerhalb von 600 s der Zeit des Verifizierers) und noch die verwendeten Daten sind. Die Prüfung gilt den Daten, nicht dem IODnav: Der Verifizierer behält den SHA-256 der authentifizierten Bitfolge (Word Types 1 bis 5, wie ADKD 0 sie abdeckt) und der neuesten solchen Folge, die der Satellit sendet, und beide müssen gleich sein. Ein kopierter IODnav über anderen Wörtern oder veränderte Bits von Word Type 5 (die keinen IODnav tragen) liest sich als Unavailable, bis die neuen Daten authentifiziert sind. Ein Subframe, der Ephemeridenwörter trägt, aber keinen vollständigen Satz der Word Types 1 bis 5 unter einem IODnav, nimmt den Status auf dieselbe Weise weg (jeder echte E1-B-Subframe trägt alle fünf); einer ganz ohne Ephemeridenwörter sagt nichts aus. Tags eines anderen Satelliten (Kreuzauthentifizierung) zählen für den Satelliten, dessen Daten sie abdecken.
  • Failed, wenn eine Prüfung der Daten des Satelliten innerhalb des Fehlergedächtnisses fehlgeschlagen ist (standardmäßig 600 s der Zeit des Verifizierers), auch wenn spätere Daten verifiziert werden. Eine Abweichung wird dem Satelliten angelastet, dessen Daten das Tag abdeckt, und dem Satelliten, der das Tag gesendet hat, da jeder von beiden fehlerhaft sein könnte; eine MACSEQ-Abweichung wird dem sendenden Satelliten angelastet.
  • Sonst Unavailable, und für jeden Satelliten nach einer verifizierten Alarmnachricht, bei NMA-Status „don't use“ oder einem reservierten Status und bis der erste Schlüssel verifiziert ist.

Der Gesamtwert ist Failed, wenn ein Satellit fehlgeschlagen ist, Authenticated, wenn mindestens einer authentifiziert ist und keiner fehlgeschlagen ist, sonst Unavailable. $PKSOS,<A|F|N>[,<sat>:<A|F|N>...] ist der passende Satz; kshana::osnma::pksos_sentence baut ihn.

Zeit#

Die Schlüsselkette zeigt, dass ein Schlüssel zu einem bestimmten Subframe gehört, nicht, dass der Subframe der gegenwärtige ist. Der Verifizierer nimmt daher „jetzt“ nie von einer nicht verifizierten Seite. Seine Zeit ist der spätere Wert aus der mit set_reference_time (--reference-time) angegebenen Referenzzeit und dem Subframe des letzten verifizierten TESLA-Schlüssels (ein Schlüssel kann nicht vor seinem Slot bekannt sein, er belegt also, dass die Zeit nicht früher liegt). Fehlergedächtnis, das Gültigkeitsfenster einer Authentifizierung und das Entfernen alter Daten laufen auf dieser Zeit. Ein Subframe mit einem Zeitstempel von mehr als drei Subframes nach dem letzten verifizierten Schlüssel wird beiseitegelegt, sofern nicht sein eigener Schlüssel seine Zeit belegt; Schlüssel- und Alarmnachrichten darin werden weiterhin verarbeitet, da sie ihren eigenen Beleg tragen (eine Signatur oder die Merkle-Wurzel).

Die Uhr steht still, wenn OSNMA aufhört, da nur Schlüssel sie vorrücken. Ein Live-Aufrufer muss set_reference_time in jedem Subframe mit der Hostzeit aufrufen; die Authentifizierung läuft dann von selbst ab, und poll_clock (ebenfalls mit jedem Subframe ausgeführt) liefert ein Ereignis KeyStall, sobald länger als das Authentifizierungsfenster kein Schlüssel verifiziert wurde.

Was dies nicht bietet, ist Aktualität. Ohne eine Referenzzeit aus einer Quelle, die ein Angreifer nicht verschieben kann, wird eine Wiedereinspielung alter authentischer Daten unter alten Zeitstempeln authentifiziert, und die Seitenzeitstempel sind nur so gut wie ihre Quelle: Die CLI sagt das (freshness: NOT enforced), sobald keine Referenzzeit angegeben ist. Mit einer Referenzzeit liegt die Toleranz unter einem Subframe (standardmäßig 29 s; größere Werte werden abgelehnt). Eine Referenzzeit ist ein einzelner Zeitpunkt: Subframes, die weiter davon entfernt sind als die Toleranz, werden beiseitegelegt und gezählt, sie eignet sich daher für Live- oder gerade aufgezeichnete Daten, nicht für eine lange Aufzeichnung. Lassen Sie bei einer Aufzeichnung die Referenzzeit weg und lesen Sie das Ergebnis als „authentisch, nicht unbedingt aktuell“. Die Receiver Guidelines geben die formale Anforderung an.

Die Seitenzeitstempel eines u-blox-Stroms stammen aus den nicht authentifizierten Daten selbst (siehe Eingabe) und bieten daher ebenfalls keinen Schutz der Aktualität.

Ketten, Schlüssel und Alarme#

Verifizierte Ketten werden nach Ketten-ID (CID) geführt; Tags werden mit der Kette geprüft, die der NMA-Header nennt, und ein Header, der eine nicht verifizierte Kette nennt, lässt die Tags des Subframes ungeprüft (chain_mismatch). Ein zweiter KROOT einer geltenden Kette fügt einen Anker hinzu; ein KROOT einer anderen Kette wird daneben behalten, sodass eine erneute Ankündigung der alten Kette nach einer Erneuerung die neue nicht aufhebt.

Der NMA-Header ist nicht authentifiziert, daher zählt das, was er ankündigt, nur, wenn es bestätigt ist: wenn mehr als ein Satellit in einem Subframe denselben Header sendet oder wenn die signierte Nachricht eines verifizierten KROOT ihn trägt (der Header ist Teil dieser Nachricht). Beides ist nicht gleichwertig. Die Übereinstimmung mehrerer Satelliten schützt vor einer Anomalie eines einzelnen Satelliten, nicht vor einem Spoofer: Ein Spoofer kann zwei Satellitenkennungen fälschen. Das bringt ihm einen Denial-of-Service, und nichts weiter, da ein bestätigtes „don't use“ die authentifizierten Daten jedes Satelliten löscht: Es aufzuheben, gefälscht oder echt, stellt nichts wieder her, bis neue Tags unter echten operativen Subframes verifiziert werden. (Vor diesem Löschen konnte eine gefälschte Aufhebung für bis zu das Authentifizierungsfenster Daten zurückbringen, die bereits authentifiziert waren.) Die echte Bestätigung ist der vom KROOT signierte Header, der ein „don't use“ auch wieder aufhebt, wenn er einen operativen Status trägt. Der Header eines einzelnen Satelliten ändert nichts außer der Tag-Prüfung seines eigenen Subframes. Ein KROOT, dessen Gültigkeitszeitpunkt nicht später liegt als der der für dieselbe ID gehaltenen Kette (eine Wiedereinspielung eines älteren), wird abgelehnt (stale); ein KROOT derselben Kette fügt einen Anker hinzu.

Der Status und der CPKS-Wert des bestätigten Headers steuern den Widerruf. Bei NMA-Status „don't use“ verwirft CPKS „chain revoked“ die Kette, die der Header nennt, und lehnt ihren KROOT von da an ab; CPKS „public key revoked“ verwirft den öffentlichen Schlüssel, der diese Kette signiert hat, und jede damit signierte Kette. Wenn die Ersatzkette für diese ID angenommen wurde, bevor der Header bestätigt wurde, wie lange davor auch immer, ist der widerrufene Schlüssel der der vorherigen Kette, nicht der der neuen; dieses Gedächtnis bleibt erhalten, bis der Widerruf ausgeführt oder der Header eines anderen Verfahrens bestätigt wird, und ein in jedem Subframe wiederholter Widerrufs-Header wird einmal ausgeführt. Dieselben CPKS-Werte mit Status operational kennzeichnen den Übergang zur Ersatzkette und widerrufen nichts. Der Header ist selbst nicht authentifiziert, daher kann ein gefälschter Widerruf nur Authentifizierung wegnehmen, und er ist endgültig, bis der Verifizierer neu gestartet wird. Diese Endgültigkeit gilt auch für den aktiven Schlüssel: Ein gefälschter Widerruf des öffentlichen Schlüssels setzt das Gedächtnis des abgelösten Schlüssels zurück und widerruft den verwendeten Schlüssel, und nur ein Neustart macht es rückgängig.

Ein DSM-PKR mit NPKT 4 ist die Alarmnachricht; sie wird wie ein Schlüssel über den Merkle-Baum verifiziert, wobei ihr zufälliges Feld NPK Teil des Blatts ist. Ein verifizierter Alarm löscht alle Ketten und öffentlichen Schlüssel, lehnt jeden späteren KROOT und öffentlichen Schlüssel ab und setzt jeden Satelliten auf Unavailable, wie die Receiver Guidelines es vorgeben, bis der Verifizierer neu gestartet wird.

Das Padding-Feld eines DSM-KROOT ist ein Hash der signierten Nachricht und der Signatur. Es wird vor der Signatur geprüft und sagt auch, mit welchem NMA-Header die signierte Nachricht aufgebaut wurde, wenn sich der Header änderte, während die Nachricht zusammengesetzt wurde.

Was nicht verifiziert oder nicht geübt wird#

  • Aktualität. Siehe Zeit oben: Sie braucht eine Referenzzeit von außen.
  • Erneuerung und Widerruf. Ketten und Schlüssel werden so verfolgt, wie die Ströme der veröffentlichten Vektoren sie üben (Erneuerung, Kettenende, Widerruf von Kette und Schlüssel, neuer öffentlicher Schlüssel, neuer Merkle-Baum, Alarm), nicht jeden Fall, den das ICD erlaubt. Ein vom nicht authentifizierten Header angekündigter Widerruf wird beim Wort genommen und ist für die Sitzung endgültig. Ein neuer Merkle-Baum wird nur über die Merkle-Wurzel übernommen, die dem Verifizierer gegeben wird.
  • Hash des Merkle-Baums. DSM-PKR-Nachrichten werden mit einem SHA-256-Baum geprüft. Ein SHA3-256-Baum, den das ICD künftig erlaubt, wird nicht unterstützt. Die Hashfunktion der Kette kann SHA-256 oder SHA3-256 sein.
  • Schlüsselstatuslisten. Der Widerruf wird über den NMA-Header und die Nachrichten im Strom verfolgt; Zertifikatsperrlisten werden nicht gelesen.
  • Abdeckung der authentifizierten Daten. ADKD 4 mit COP 1, in einem ungeraden Subframe gesendet, kann nicht authentifizieren (Word Type 10 kann ein oder zwei Subframes früher gesendet werden); das kostet nur Verfügbarkeit, nie Korrektheit.
  • Betrieb in konstanter Zeit. Nicht vorgesehen und nicht nötig: Der Verifizierer hält keine Geheimnisse (nur öffentliche Schlüssel, veröffentlichte Kettenschlüssel und empfangene Daten).
  • Signal- und Datenqualität. Seiten werden per CRC geprüft und Alarmseiten verworfen, aber über das Signal, auf dem sie ankamen, ist nichts bekannt. Die Reed-Solomon-Wiederherstellung der I/NAV-Wörter 1 bis 4 wird nicht verwendet: Die Daten müssen direkt empfangen werden.
  • Kein Dienst für die Positionsintegrität. Authentifizierung besagt, dass die Daten vom System stammen. Sie sagt nichts über das Signal, auf dem sie ankamen, oder über die Güte der Lösung.

Eingabe#

  • Einfaches Seitenformat. Ein Seitenpaar pro Zeile: <svid> <gst_seconds> <60 hex digits>, Kommentare mit # erlaubt. gst_seconds ist GST in Sekunden (week * 604800 + time of week) zu Beginn der Seite; das Hex sind die 240 Bits der geraden und der ungeraden Hälfte (Tails eingeschlossen), höchstwertiges Bit zuerst.
  • Testvektor-CSV. Das Layout der veröffentlichten Vektoren (SVID,NumNavBits,NavBitsHEX). Die Startzeit stammt aus --start-gst oder aus einem datierten Dateinamen.
  • u-blox UBX-RXM-SFRBX. Ein roher UBX-Bytestrom (--format ubx, automatisch erkannt). Galileo-E1-B-I/NAV-Nachrichten werden zu Seitenpaaren neu aufgebaut (src/osnma/ubx.rs); andere Systeme, E5b, Alarmseiten und Rahmen mit falscher Prüfsumme werden ignoriert. SFRBX trägt keine Zeit, daher wird jede Seite aus der Zeit in ihren eigenen Daten (Word Types 5 und 0) gestempelt und nur akzeptiert, wenn aufeinanderfolgende zeittragende Seiten mit der Anzahl der dazwischen empfangenen Seiten übereinstimmen; Seiten, die nicht eingeordnet werden können, werden verworfen, nie erraten. Diese Zeit stammt aus den Daten selbst und bietet keinen Schutz der Aktualität (siehe Zeit oben). Die Signal-ID 0 (E1-C) trägt keine Daten, daher ist es harmlos, sie neben E1-B zu akzeptieren. Ein vereinzeltes Synchronisationspaar mit absurder Länge hält den Leser nicht auf. Der Framing-Code ist eine dritte Kopie des UBX-Framings im Baum (src/realdata/ubx.rs, die Eingabe des Empfänger-Vertrauens); er wird getrennt gehalten, damit das Verhalten des Empfänger-Vertrauens unverändert bleibt, und die drei könnten später einen Leser teilen. Das Wortlayout einer SFRBX-Nachricht stammt aus öffentlichen Beschreibungen und wurde nur an synthetischen Rahmen geprüft, nicht an einem Live-Empfänger.

Befehlszeile#

kshana osnma verify <input> [--format pages|vector-csv|ubx] [--start-gst SECONDS]
    [--merkle-root HEX] [--public-key ID:TYPE:HEX]
    [--reference-time SECONDS [--max-time-error SECONDS]] [--epochs] [--json]

Die Textausgabe gibt den Status je Satellit und, mit --epochs, je Tag und Epoche an; --json gibt dasselbe mit der Tag-Liste. Exit-Status 3 bedeutet, dass eine Prüfung der Daten eines Satelliten fehlgeschlagen ist, 2 bedeutet einen Aufruf- oder Eingabefehler. Der Hinweis (beratend) oben wird auf jedem Pfad zuerst ausgegeben, Fehler und Aufrufhilfe eingeschlossen.

Aus Python und WebAssembly#

kshana.osnma_verify(data, format="", start_gst=None, merkle_root_hex="", public_keys=None, reference_time=None, max_time_error=None) (Python) und osnma_verify(input_base64, format, start_gst, merkle_root_hex, public_keys, reference_time, max_time_error) (WebAssembly) führen denselben Verifizierer auf Bytes im Speicher aus und geben das JSON zurück, das kshana osnma verify --json ausgibt, einschließlich des Hinweises (beratend). Der MCP-Server trägt es nicht: Die Eingabe ist ein langer roher Empfängerstrom, der mit der Kommandozeile und Python besser bedient ist, und ein Agentenwerkzeug würde eine große Eingabeoberfläche ohne Nutzen hinzufügen. Derselbe Hinweis und dieselben Grenzen gelten; der Status ist kein Navigationsintegritätsdienst.

Tests und Testdaten#

Die Testdaten sind synthetisch und werden vom eigenen Code und den eigenen Tests von Kshana erzeugt. In diesem Repository sind keine Vektoren, durchgerechneten Beispiele oder Schlüsselmaterial aus dem ICD oder den Guidelines gespeichert. Die Standardtests (tests/osnma_synthetic.rs und die Unit-Tests) bauen aus den Gleichungen eine Kette, Seiten und Tags auf und prüfen die Konsistenz sowie dass absichtliche Beschädigung erkannt wird: ein gekipptes Navigationsbit, ein gekipptes Schlüsselbit, wiedereingespielte Tags, falsche Zeitstempel, eine Zeit außerhalb der Referenz, fehlende Daten und die Angriffe, die die Prüfung fand (ein gefälschter Subframe in ferner Zukunft, der einen Fehlschlag nicht löschen darf, eine Authentifizierung, die ablaufen und dem IODnav folgen muss, ein Alarm, NMA-Status „don't use“, Dummy-Tags, MACSEQ-Abweichung, widersprüchliche Schlüssel, Kettenwiderruf und -erneuerung, ein schlechtes Padding oder eine schlechte Signatur), wobei CMAC-AES, eine SHA3-256-Kette und eine P-521-Signatur durch den Verifizierer geführt werden. Da diese Daten aus unserer eigenen Lesart des ICD stammen, zeigen sie Selbstkonsistenz, nicht Konformität.

Die Primitive werden gegen veröffentlichte Known-Answer-Tests geprüft, von denen keiner aus dem ICD stammt: RFC 4231 (HMAC-SHA-256), RFC 4493 (CMAC-AES-128), FIPS 197 (AES), FIPS 202 (SHA3-256) und RFC 6979 (ECDSA P-256). P-521-Signaturen werden auf Kodierung und Hash-Auswahl mit einem im Test erzeugten Wegwerfschlüssel geprüft; die Kurvenarithmetik stammt aus der Bibliothek.

Die Übereinstimmung mit den offiziellen Vektoren wird von tests/osnma_official_vectors.rs geprüft, das optional ist: Es liest eine Kopie des offiziellen Testvektor-Archivs, die Sie selbst herunterladen, aus dem Verzeichnis, das KSHANA_OSNMA_VECTORS nennt, und tut nichts, wenn die Variable nicht gesetzt ist. Es speichert und verbreitet nichts, und Standardtestläufe verwenden kein Netzwerk. Es gibt dem Verifizierer nur eine Merkle-Wurzel und die öffentlichen Schlüssel eines Baums, wie ein Empfänger sie hätte, sodass Ketten aus dem signierten DSM-KROOT im Strom aufgebaut werden und der ganze Weg läuft: Merkle, Signatur, Schlüsselkette, MACSEQ und Tags. Es stellt je Vektorsatz fest: eine Untergrenze für die authentifizierten Tags, keines fehlgeschlagen, welche Ketten und Schlüssel der Strom ankündigt, authentifizierte Tags von ADKD 0, 4 und 12, dass die Authentifizierung nach Kettenende, Kettenwiderruf, Schlüsselwiderruf und neuem öffentlichen Schlüssel wieder einsetzt, dass die Alarmnachricht verifiziert wird und nichts authentifiziert zurücklässt, und dass jeder Vektor nur HMAC-SHA-256, eine SHA-256-Kette, 128-Bit-Schlüssel, 40-Bit-Tags und die Nachschlageeinträge 33 und 34 verwendet. Andere Algorithmen- und Parameterwahlen des ICD sind daher nur von den obigen Known-Answer- und synthetischen Tests abgedeckt.

Abhängigkeiten#

RustCrypto-Crates, reines Rust, MIT OR Apache-2.0, ohne Standard-Features gebaut: hmac, cmac, aes, sha3, p256 und p521 (das Feature ecdsa, nur zur Verifikation verwendet), neben sha2, das bereits im Baum ist. Sie bauen für wasm32-unknown-unknown und bestehen cargo deny.