{
  "name": "DVeProto",
  "version": 1.5,
  "handshake_version": 1,
  "generation": "1.x LTS",
  "lts": true,
  "canonical_url": "https://dveproto.serversys.ru",
  "documentation": {
    "human": "./",
    "note": "Human docs ship with this folder; drop on any domain."
  },
  "tagline": "DVeProto 1.5 LTS is the final release of the first-generation transport protocol. Future network-layer features are developed in DVeProto 2.x.",
  "description": "Application-layer framing: X25519 ECDH, HKDF-SHA256, AES-256-GCM. Package 1.5 LTS freezes wire 1.4 (ver=0x14). DVeNet/DVeVPN and native UDP/TCP carriers arrive in 2.0.",
  "architecture": {
    "1.x": [
      "Application",
      "RPC / APP_JSON",
      "DVeProto 1.4 / 1.5 LTS",
      "Carrier: WebSocket (production) | length-prefixed TCP / UDP datagrams (defined for 2.0, same frame body)",
      "Internet"
    ],
    "2.0": [
      "Application",
      "DVeNet (overlay + Transport Manager)",
      "DVeProto 2.0",
      "UDP (primary) | TCP | WebSocket | Tor (fallback)",
      "Internet"
    ],
    "transport_agnostic_frames": "Encrypted frame bodies (ver…ciphertext||tag) are independent of the carrier. WebSocket: one binary message = one frame. TCP (2.0): u32be length prefix + frame. UDP (2.0): one datagram = one frame (MTU-aware). 1.4+ implementations MUST treat the frame as opaque bytes so 2.0 carriers can reuse the same codec."
  },
  "roadmap": {
    "1.0": "JSON text frames",
    "1.1": "Binary frames + FILE_*",
    "1.2": "stream_id mux, PING/PONG/CLOSE, reserved NET/VPN ptypes",
    "1.3": "AES-GCM AAD, counter nonces, WINDOW_UPDATE flow control",
    "1.4": "Session Resume, Stream Priority/Cancel, DATA_ACK, KeepAlive RTT, Capability Flags, Packet Sequence, Statistics API",
    "1.5": "LTS — Session Ticket Store + auto Resume, test vectors, Security Considerations, load validation, Wire 1.x freeze (binary layout stays 1.4 / ver=0x14)",
    "2.0": "Carrier abstraction + DVeNet + DVeVPN + UDP-primary + multipath + NAT + routing + compress/crypto negotiation + QoS + Plugin API + DVeRPC + Discovery + Session Migration"
  },
  "roadmap_2_0": {
    "status": "rc",
    "alpha_version": "2.0.0-alpha",
    "beta_version": "2.0.0-beta",
    "document": "./roadmap-2.0.md",
    "alpha": {
      "status": "released",
      "label": "DVeProto 2.0 Alpha Released",
      "released": true,
      "spec_frozen": true,
      "alpha_version": "2.0.0-alpha",
      "released_date": "2026-08-02",
      "freeze_report": "./dveproto-tests/ALPHA_FREEZE_REPORT.md",
      "release_name_when_ready": "DVeProto 2.0 Alpha Released",
      "checklist": "./dveproto-tests/ALPHA_CANDIDATE.md",
      "release_gate": "./dveproto-tests/RELEASE_GATE.md",
      "post_alpha_proposals": "./dveproto-tests/POST_ALPHA_PROPOSALS.md",
      "exit_criteria": "./dveproto-tests/EXIT_CRITERIA.md",
      "control_model": {
        "specification": "what must be (spec.json, wire/handshake)",
        "roadmap": "in what order (roadmap-2.0.md)",
        "release_gate": "may we advance (RELEASE_GATE.md, EXIT_CRITERIA.md)"
      },
      "exit_criteria_checker": "python dveproto-tests/check_exit_criteria.py",
      "ci_workflow": "./.github/workflows/dveproto-alpha.yml",
      "ci_matrix": {
        "python": [
          "3.11",
          "3.12",
          "3.13"
        ],
        "node": [
          "20",
          "22"
        ],
        "os": [
          "ubuntu-latest",
          "windows-latest"
        ]
      },
      "compatibility_policy": "./COMPATIBILITY.md",
      "changelog": "./CHANGELOG.md",
      "freeze_report_template": "./dveproto-tests/ALPHA_FREEZE_REPORT.md",
      "beta_implementation_plan": "./dveproto-tests/BETA_IMPLEMENTATION_PLAN.md",
      "beta_entry": "./dveproto-tests/BETA_ENTRY.md",
      "beta_exit": "./dveproto-tests/BETA_EXIT.md",
      "dvenet_scaffold": "./v2/dvenet.py",
      "lifecycle": [
        "development",
        "alpha_candidate",
        "alpha_released",
        "beta",
        "release_candidate",
        "stable"
      ],
      "promotion": "Alpha Released. Beta Entry opened — Phase 6 Discovery+Peers.",
      "goal": "Carrier + Wire + Handshake + Interop — frozen Alpha foundation",
      "ac1_done": [
        "python_vectors",
        "python_live_py_py",
        "negative_tests",
        "fuzz_tests"
      ],
      "interop_verified": [
        "python_python",
        "python_node",
        "browser_class_ws",
        "browser_node_vectors",
        "negative_tests",
        "fuzz_tests"
      ],
      "browser_browser_p2p": "deferred",
      "before_beta": [
        "alpha_released",
        "freeze_report_filled",
        "beta_entry"
      ],
      "not_in_scope_until_beta": [
        "multipath",
        "nat_traversal",
        "routing",
        "vpn",
        "discovery",
        "relay",
        "dvenet_mesh"
      ],
      "artifacts": [
        "./v2/",
        "./dveproto-tests/",
        "./v2/test_vectors.json",
        "./COMPATIBILITY.md",
        "./CHANGELOG.md"
      ],
      "smoke": "python dveproto-tests/run_all.py",
      "gen_vectors": "python -m v2.gen_test_vectors"
    },
    "beta": {
      "status": "development",
      "opened": "2026-08-02",
      "entry": "./dveproto-tests/BETA_ENTRY.md",
      "plan": "./dveproto-tests/BETA_IMPLEMENTATION_PLAN.md",
      "module": "./v2/dvenet.py",
      "current_milestone": "phase6_discovery_peers",
      "milestone_goal": "Node A → Discovery → Node B → Handshake → Encrypted DVeNet channel",
      "includes": [
        "node_advertisement",
        "peer_database",
        "static_bootstrap",
        "direct_announce",
        "peer_states"
      ],
      "not_now": [
        "routing",
        "mesh_flood",
        "multipath",
        "vpn",
        "acl",
        "relay",
        "lan_dht"
      ],
      "phases": [
        {
          "phase": 6,
          "name": "Discovery + Peers",
          "status": "done"
        },
        {
          "phase": 7,
          "name": "Routing",
          "status": "done"
        },
        {
          "phase": 8,
          "name": "Multipath",
          "status": "done"
        },
        {
          "phase": 9,
          "name": "DVeVPN",
          "status": "planned"
        }
      ],
      "test": "python dveproto-tests/dvenet/test_discovery_channel.py"
    },
    "milestones": {
      "alpha_candidate": [
        "Carrier",
        "Wire",
        "Handshake",
        "AC1 tests"
      ],
      "alpha_released": [
        "full Interop matrix",
        "freeze report"
      ],
      "beta": [
        "Discovery+Peers",
        "Routing",
        "Discovery extras",
        "then Multipath/VPN toward RC"
      ],
      "rc": [
        "Multipath",
        "VPN",
        "Performance"
      ],
      "stable": [
        "Production"
      ]
    },
    "principle": "Wire 1.x LTS frozen. Alpha 2.0 frozen. Beta additive only on Alpha stack.",
    "goal": "Universal transport platform for DVeNet, DVeVPN, FlowChat, RPC, File Transfer, and future ServerSystem services.",
    "node_identity": {
      "node_id": "Ed25519 public key (32 bytes) — stable node identity",
      "connection_id": "8 random bytes — per path/session; one Node may have many ConnectionIDs"
    },
    "handshake_2_0": {
      "flow": [
        "ClientHello",
        "ServerHello",
        "X25519 ECDH",
        "HKDF-SHA256 session keys",
        "Finished",
        "Wire 2.0 encrypted frames"
      ],
      "primitives": {
        "ecdh": "X25519",
        "kdf": "HKDF-SHA256",
        "labels": [
          "DVeProto-v2/c2s",
          "DVeProto-v2/s2c",
          "DVeProto-v2/finished"
        ],
        "aead": [
          "AES-256-GCM (0x0001)",
          "ChaCha20-Poly1305 (0x0002)"
        ],
        "node_auth": "Ed25519 (ServerHello signs SHA-256 of ClientHello||ServerHello-unsigned)"
      },
      "module": [
        "./v2/handshake.py",
        "./v2/handshake.js"
      ]
    },
    "interop": {
      "vectors": "./v2/test_vectors.json",
      "suite": "./dveproto-tests/",
      "criteria": [
        "Identical shared_secret, HKDF, Finished MAC, ciphertext, tag on Python/Browser/Node",
        "Cross matrix (not only Python→Python)",
        "Live: Python server ↔ JS client Handshake→Ping→Pong→Close"
      ],
      "runners": {
        "python": "python dveproto-tests/handshake/test_vectors.py",
        "node": "node dveproto-tests/handshake/test_vectors.mjs",
        "browser": "./dveproto-tests/handshake/browser.html",
        "live_py_node": "python dveproto-tests/interop/live_py_node.py",
        "live_py_browser_ws": "node dveproto-tests/interop/live_py_browser_node.mjs",
        "negative": "python dveproto-tests/negative/test_negative.py",
        "fuzz": "python dveproto-tests/fuzz/test_fuzz_wire.py",
        "all": "python dveproto-tests/run_all.py"
      }
    },
    "implementation_phases": [
      {
        "phase": 1,
        "name": "Carrier API",
        "status": "done",
        "milestone": "alpha"
      },
      {
        "phase": 2,
        "name": "Wire 2.0",
        "status": "done",
        "milestone": "alpha"
      },
      {
        "phase": 3,
        "name": "UDP/TCP",
        "status": "done",
        "milestone": "alpha"
      },
      {
        "phase": 4,
        "name": "Handshake 2.0",
        "status": "done",
        "milestone": "alpha"
      },
      {
        "phase": 5,
        "name": "Interop",
        "status": "done",
        "milestone": "alpha",
        "note": "Alpha Released 2026-08-02; surfaces frozen."
      },
      {
        "phase": 6,
        "name": "DVeNet Mesh",
        "milestone": "beta",
        "blocked_on": "beta_entry"
      },
      {
        "phase": 7,
        "name": "Routing",
        "milestone": "beta",
        "status": "done"
      },
      {
        "phase": 8,
        "name": "Discovery",
        "milestone": "beta"
      },
      {
        "phase": 9,
        "name": "Multipath",
        "milestone": "rc"
      },
      {
        "phase": 10,
        "name": "DVeVPN",
        "milestone": "rc",
        "stack": "IP → DVeVPN → DVeNet → Carrier → Internet"
      },
      {
        "phase": 11,
        "name": "Performance",
        "milestone": "rc"
      },
      {
        "phase": 12,
        "name": "Production / 2.0 Stable",
        "milestone": "stable"
      }
    ],
    "smart_transport": {
      "summary": "Carrier emits events + stats; DVeNet switches or bonds carriers.",
      "metrics": [
        "rtt_ms",
        "jitter_ms",
        "packet_loss",
        "bandwidth_up",
        "bandwidth_down",
        "mtu",
        "state"
      ],
      "behaviors": [
        "UDP congested → fall back to TCP",
        "UDP healthy again → return to UDP",
        "UDP + TCP simultaneously (multipath bond, post-mesh)"
      ]
    },
    "architecture": [
      "Applications",
      "FlowChat / DVeRPC / File Transfer",
      "DVeNet (+ Smart Transport) — phase 6+",
      "DVeVPN (optional, phase 9)",
      "Handshake 2.0 + Wire 2.0",
      "Carrier Layer: UDP | TCP",
      "Internet"
    ],
    "pillars": {
      "carrier_abstraction": {
        "carriers": [
          "UDP",
          "TCP",
          "WebSocket",
          "TLS",
          "Tor/SOCKS5",
          "plugin carriers"
        ],
        "rule": "Same DVe frame body on every carrier."
      },
      "dvenet": {
        "features": [
          "Node ID",
          "peer table",
          "neighbor discovery",
          "multi-hop routing",
          "keepalive",
          "connection recovery"
        ],
        "frames": [
          "NODE_HELLO",
          "NODE_INFO",
          "NODE_UPDATE",
          "ROUTE_REQUEST",
          "ROUTE_REPLY",
          "ROUTE_REMOVE",
          "NODE_PING",
          "NODE_PONG"
        ]
      },
      "dvevpn": {
        "payloads": [
          "IPv4",
          "IPv6",
          "Ethernet optional"
        ],
        "ptypes": [
          "VPN_PACKET",
          "VPN_CONTROL"
        ],
        "features": [
          "split tunnel",
          "full tunnel",
          "routes",
          "DNS over VPN",
          "MTU negotiation"
        ]
      },
      "udp_transport": [
        "loss detection",
        "reorder handling",
        "congestion control",
        "selective retransmission",
        "packet pacing"
      ],
      "multipath": [
        "channel aggregation",
        "failover",
        "balancing",
        "backup paths"
      ],
      "nat_traversal": [
        "UDP hole punching",
        "relay",
        "STUN",
        "TURN optional"
      ],
      "routing": [
        "neighbors",
        "path cost",
        "RTT",
        "loss",
        "best-path selection"
      ],
      "compression": [
        "none",
        "zstd",
        "gzip legacy"
      ],
      "crypto_negotiation": [
        "AES-256-GCM",
        "ChaCha20-Poly1305",
        "pluggable AEAD"
      ],
      "qos_priorities": {
        "0": "control",
        "1": "chat",
        "2": "voice",
        "3": "video",
        "4": "files"
      },
      "statistics": [
        "RTT",
        "jitter",
        "packet loss",
        "upload",
        "download",
        "active streams",
        "active routes",
        "carrier",
        "multipath status",
        "cipher"
      ],
      "plugin_api": [
        "carrier",
        "compression",
        "crypto",
        "routing",
        "vpn",
        "relay"
      ],
      "dverpc": [
        "request",
        "response",
        "notify",
        "stream",
        "cancel"
      ],
      "dvediscovery": [
        "LAN",
        "relay",
        "bootstrap"
      ],
      "session_migration": "Continue session across IP/carrier changes without a new full handshake."
    },
    "production_goals": {
      "max_concurrent_connections_per_server": 10000,
      "overhead": "minimal",
      "architecture": "fully async",
      "transport_independence": true,
      "interop": "all DVeProto 2.x implementations"
    },
    "rc_version": "2.0.0",
    "protocol_frozen": true
  },
  "versioning_policy": "1.5 is LTS: same binary wire as 1.4 (ver=0x14). Wire format is FROZEN — changes only in 2.x. select \"1.5\" ≡ wire 1.4.",
  "wire_freeze": {
    "status": "frozen",
    "binary_ver": "0x14",
    "frozen_at": "1.5",
    "includes": [
      "headers (ver|ptype|stream_id|pkt_seq|nonce)",
      "ptype map 0x01–0x38 and reserved 0x20–0x22",
      "AAD layouts for 1.3 and 1.4",
      "nonce = prefix4 || counter u64be",
      "packet sequence u32be",
      "stream_id u16be",
      "handshake / resume / dve_resume_reject / dve_resume_ok (APP_JSON)"
    ],
    "rule": "After 1.5 LTS, any Wire change requires DVeProto 2.x."
  },
  "production_api": {
    "python": [
      "session.send(obj, stream_id=0)",
      "session.receive(message) → DecodedFrame | dict",
      "session.stats()",
      "DVeClientSession.resume(hello, session_id, token)",
      "session.close(code=0, reason=b'')"
    ],
    "javascript": [
      "await session.send(obj, streamId?)",
      "await session.receive(message)",
      "session.stats()",
      "await DVeProtoSession.resume(hello, sessionId, token)",
      "await session.close(code?, reason?)"
    ]
  },
  "security_limits": {
    "max_plaintext_bytes": 1048576,
    "max_frame_bytes": 1048596,
    "session_idle_timeout_s": 300,
    "session_max_lifetime_s": 86400,
    "replay_window": 64,
    "ticket_ttl_default_s": 86400,
    "ticket_ttl_min_s": 60,
    "ticket_ttl_max_s": 604800,
    "rekey_after_bytes": 536870912,
    "rekey": "Ticket-based: SESSION_TICKET with expire_unix=0 triggers immediate rekey_from_token; otherwise rotate on Resume."
  },
  "lts_1_5_checklist": {
    "wire_freeze": "done",
    "ticket_store": "done",
    "auto_resume": "done — dve_resume_ok + stream list",
    "security_hardening": "done — limits, timeouts, replay window, ticket TTL, rekey",
    "test_vectors": "done — test_vectors.json",
    "production_api": "done",
    "interop_tests": "reference smoke + vectors",
    "load_testing": "recommended operational gate (10/100/1000 streams)"
  },
  "primitives": {
    "ecdh": "X25519",
    "hkdf": "HKDF-SHA256",
    "hkdf_salt": "",
    "hkdf_info_client_to_server": "DVeProto-v1/c2s",
    "hkdf_info_server_to_client": "DVeProto-v1/s2c",
    "hkdf_info_resume_c2s": "DVeProto-v1/resume-c2s",
    "hkdf_info_resume_s2c": "DVeProto-v1/resume-s2c",
    "aead": "AES-256-GCM",
    "nonce_length_bytes": 12
  },
  "wire_negotiation": {
    "hello_field": "offer",
    "ack_field": "select",
    "values": [
      "1.0",
      "1.1",
      "1.2",
      "1.3",
      "1.4",
      "1.5"
    ],
    "default_if_absent": "1.0",
    "note": "dve_hello may include offer:[\"1.0\"…\"1.5\"] and caps (u32). select 1.5 ≡ wire 1.4 (ver=0x14). Omit select → 1.0. Handshake v stays 1."
  },
  "capability_flags": {
    "field": "caps",
    "type": "u32",
    "bits": {
      "0": "resume — Session Resume / SESSION_TICKET",
      "1": "priority — STREAM_PRIORITY",
      "2": "data_ack — DATA_ACK",
      "3": "cancel — STREAM_CANCEL",
      "4": "rtt — PING timestamp for RTT",
      "5": "pkt_seq — packet sequence in header (always on for wire 1.4)",
      "6": "compress — reserved",
      "7": "zstd — reserved",
      "8": "udp — peer can use UDP carrier (negotiated in 2.0)",
      "9": "tcp — peer can use length-prefixed TCP carrier (negotiated in 2.0)",
      "10": "multipath — reserved 2.0",
      "11": "websocket — WebSocket carrier (default in 1.x)"
    },
    "default_1_4": 2111,
    "default_1_4_note": "bits 0–5 + bit 11 (resume|priority|data_ack|cancel|rtt|pkt_seq|websocket) = 0x083F. UDP/TCP bits are advertised only when a carrier is actually available; 1.4 docs reserve them for 2.0."
  },
  "transport": {
    "principle": "Handshake is UTF-8 JSON. select 1.1+ → binary frames only. select 1.0 → JSON text frames. Frame codec is carrier-agnostic.",
    "examples": {
      "websocket_handshake": "Text frames: dve_hello then dve_client_ack (or resume ack).",
      "websocket_data_1_0": "One JSON string per text frame: type dve with base64 n/c.",
      "websocket_data_1_1": "One binary frame: ver=0x11|ptype|nonce|ciphertext||tag.",
      "websocket_data_1_2": "One binary frame: ver=0x12|ptype|stream_id|nonce|ciphertext||tag.",
      "websocket_data_1_3": "ver=0x13|ptype|stream_id|nonce(prefix4||counter8)|ct||tag; AAD=ver|ptype|stream_id.",
      "websocket_data_1_4": "ver=0x14|ptype|stream_id|pkt_seq|nonce|ct||tag; AAD=ver|ptype|stream_id|pkt_seq.",
      "tcp_2_0": "u32be length + same binary frame body as 1.4/2.0.",
      "udp_2_0": "One datagram = one frame; path MTU and FEC are 2.0 concerns.",
      "https": "Handshake can stay JSON over POST; binary wires need a binary-capable channel."
    }
  },
  "handshake": {
    "order": {
      "server_sends": {
        "type": "dve_hello",
        "fields": [
          "proto",
          "v",
          "server_pk",
          "offer?",
          "caps?"
        ],
        "server_pk_encoding": "base64",
        "server_pk_bytes": 32,
        "offer": [
          "1.0",
          "1.1",
          "1.2",
          "1.3",
          "1.4"
        ],
        "caps": "u32 capability bitmask (optional; default 0 if absent)"
      },
      "client_sends_full": {
        "type": "dve_client_ack",
        "fields": [
          "proto",
          "v",
          "client_pk",
          "select?",
          "caps?"
        ],
        "client_pk_encoding": "base64",
        "client_pk_bytes": 32,
        "select": "1.0|1.1|1.2|1.3|1.4"
      },
      "client_sends_resume": {
        "type": "dve_client_ack",
        "fields": [
          "proto",
          "v",
          "select",
          "resume"
        ],
        "resume": {
          "session_id": "base64 16 bytes",
          "token": "base64 32 bytes"
        },
        "note": "No client_pk. Keys = HKDF(token, resume-c2s|resume-s2c). On failure server sends dve_resume_reject (JSON text) and client MUST full-handshake."
      },
      "server_resume_reject": {
        "type": "dve_resume_reject",
        "fields": [
          "proto",
          "v",
          "reason"
        ],
        "note": "Sent as plain JSON text before any encrypted session exists."
      }
    },
    "note": "Full handshake: fresh X25519 per session; HKDF labels DVeProto-v1/c2s|s2c. Resume: HKDF labels DVeProto-v1/resume-c2s|resume-s2c over the 32-byte token."
  },
  "session_resume": {
    "wire": "1.4+",
    "ticket_ptype": "0x37 SESSION_TICKET",
    "plaintext": "session_id[16] | expire_unix u32be | token[32]",
    "flow": "After full 1.4 handshake, server SHOULD send SESSION_TICKET. Client stores session_id+token. On reconnect, client uses resume ack. Server maps session_id→token; mismatch or expiry → dve_resume_reject.",
    "security": "Token is high-entropy (32 random bytes). Store hashed at rest on server if possible; reference uses opaque token lookup."
  },
  "data_frame_1_0": {
    "wire": "1.0",
    "fields": {
      "proto": "DVeProto",
      "v": 1,
      "n": "12-byte nonce, base64",
      "c": "AES-GCM ciphertext (nonce not included in c), base64"
    },
    "plaintext": "UTF-8 JSON object; compact serialization preferred.",
    "direction": {
      "client_to_server": "encrypt with HKDF-derived c2s key",
      "server_to_client": "encrypt with HKDF-derived s2c key"
    }
  },
  "data_frame_1_1": {
    "wire": "1.1",
    "transport": "WebSocket binary frame (opcode 0x2)",
    "layout": {
      "offset_0": "uint8 ver = 0x11",
      "offset_1": "uint8 ptype",
      "offset_2": "12 bytes nonce",
      "offset_14": "ciphertext || 16-byte GCM tag"
    },
    "no_json": true,
    "no_base64": true,
    "ptypes": {
      "0x01": "APP_JSON",
      "0x02": "APP_BIN",
      "0x03": "TEXT",
      "0x10": "FILE_BEGIN",
      "0x11": "FILE_CHUNK",
      "0x12": "FILE_END",
      "0x13": "FILE_ABORT",
      "0x14": "FILE_ACK",
      "0x15": "FILE_RESUME"
    }
  },
  "data_frame_1_2": {
    "wire": "1.2",
    "layout": {
      "offset_0": "uint8 ver = 0x12",
      "offset_1": "uint8 ptype",
      "offset_2": "uint16be stream_id",
      "offset_4": "12 bytes nonce",
      "offset_16": "ciphertext || 16-byte GCM tag"
    },
    "ptypes": {
      "0x01": "APP_JSON",
      "0x02": "APP_BIN",
      "0x03": "TEXT",
      "0x10": "FILE_BEGIN",
      "0x11": "FILE_CHUNK",
      "0x12": "FILE_END",
      "0x13": "FILE_ABORT",
      "0x14": "FILE_ACK",
      "0x15": "FILE_RESUME",
      "0x20": "NET_CTRL — reserved DVeNet 2.0",
      "0x21": "NET_PKT — reserved DVeNet 2.0",
      "0x22": "VPN_PKT — reserved DVeVPN 2.0",
      "0x30": "PING",
      "0x31": "PONG",
      "0x32": "CLOSE"
    }
  },
  "data_frame_1_3": {
    "wire": "1.3",
    "layout": {
      "offset_0": "uint8 ver = 0x13",
      "offset_1": "uint8 ptype",
      "offset_2": "uint16be stream_id",
      "offset_4": "12 bytes nonce = prefix4 || counter u64be",
      "offset_16": "ciphertext || 16-byte GCM tag"
    },
    "aad": "AES-GCM additionalData = ver | ptype | stream_id u16be (4 bytes)",
    "flow_control": {
      "ptype": "0x33 WINDOW_UPDATE",
      "plaintext": "credit u32be",
      "initial_window": 262144
    },
    "inherits": "All 1.2 ptypes and stream_id mux semantics"
  },
  "data_frame_1_4": {
    "wire": "1.4",
    "final_1x": true,
    "transport": "Carrier-agnostic binary frame (WS today; TCP/UDP in 2.0)",
    "layout": {
      "offset_0": "uint8 ver = 0x14",
      "offset_1": "uint8 ptype",
      "offset_2": "uint16be stream_id",
      "offset_4": "uint32be pkt_seq (per-direction, starts at 1)",
      "offset_8": "12 bytes nonce = prefix4 || counter u64be",
      "offset_20": "ciphertext || 16-byte GCM tag"
    },
    "header_len": 20,
    "aad": "AES-GCM additionalData = ver | ptype | stream_id u16be | pkt_seq u32be (8 bytes)",
    "nonce": "Per-direction: 4-byte random prefix + monotonic u64be counter starting at 1",
    "inherits": "1.3 flow control (WINDOW_UPDATE) + all prior ptypes",
    "flow_control_exempt": [
      "PING",
      "PONG",
      "CLOSE",
      "WINDOW_UPDATE",
      "STREAM_PRIORITY",
      "STREAM_CANCEL",
      "DATA_ACK",
      "SESSION_TICKET",
      "CAPS"
    ],
    "ptypes_extra": {
      "0x34": "STREAM_PRIORITY — priority u8 (0=control … 3=files)",
      "0x35": "STREAM_CANCEL — reason u16be | note_len u16be | note",
      "0x36": "DATA_ACK — last_seq u32be (highest contiguous pkt_seq on this stream)",
      "0x37": "SESSION_TICKET — session_id[16] | expire_unix u32be | token[32]",
      "0x38": "CAPS — caps u32be (optional post-handshake capability update)"
    },
    "priorities": {
      "0": "control / protocol",
      "1": "chat / RPC (default for APP_JSON)",
      "2": "voice / realtime",
      "3": "files / bulk (default for FILE_*)"
    },
    "ping_rtt": {
      "PING_plaintext": "timestamp_ms u64be [| opaque]",
      "PONG_plaintext": "echo PING plaintext unchanged",
      "rtt_ms": "now_ms - timestamp_ms from PONG"
    },
    "statistics_api": {
      "note": "Library-level; not a wire ptype. Implementations SHOULD expose:",
      "fields": [
        "rtt_ms",
        "rtt_jitter_ms",
        "pkt_loss_estimate",
        "send_window",
        "streams",
        "uptime_s",
        "pkt_seq_send",
        "pkt_seq_recv",
        "bytes_sent",
        "bytes_recv"
      ]
    }
  },
  "file_transfer_1_1": {
    "model": "Chunked: FILE_BEGIN → FILE_CHUNK* → FILE_END; each chunk is a separate binary frame encrypted with its own AES-GCM nonce/tag.",
    "resume": "Receiver tracks last contiguous chunk index; sender may send FILE_RESUME(from_chunk); receiver answers FILE_ACK.",
    "default_chunk_size": 262144,
    "plaintext": {
      "FILE_BEGIN": "transfer_id[16] | file_size u64be | chunk_size u32be | total_chunks u32be | flags u8 | name_len u16be | name | mime_len u16be | mime | [sha256 32 if flags&1]",
      "FILE_CHUNK": "transfer_id[16] | chunk_index u32be | data",
      "FILE_END": "transfer_id[16] | sha256[32]",
      "FILE_ABORT": "transfer_id[16] | reason_code u8",
      "FILE_ACK": "transfer_id[16] | status u8 | last_ok u32be",
      "FILE_RESUME": "transfer_id[16] | from_chunk u32be"
    },
    "ack_status": [
      "ok_continue",
      "need_resume",
      "complete",
      "error"
    ]
  },
  "reference_downloads": {
    "python3_latest": "./reference.py",
    "browser_esm_latest": "./reference-client.js",
    "python3_by_wire": {
      "1.0": "./reference-1.0.py",
      "1.1": "./reference-1.1.py",
      "1.2": "./reference-1.2.py",
      "1.3": "./reference-1.3.py",
      "1.4": "./reference-1.4.py"
    },
    "browser_esm_by_wire": {
      "1.0": "./reference-1.0.js",
      "1.1": "./reference-1.1.js",
      "1.2": "./reference-1.2.js",
      "1.3": "./reference-1.3.js",
      "1.4": "./reference-1.4.js"
    }
  },
  "developer_experience": {
    "client_python": [
      "DVeClientSession.from_server_hello_text(hello_json, prefer=\"1.4\")",
      "session.pack_ping_rtt() / note_pong_rtt() / stats()",
      "session.issue_session_ticket() / from_resume(...)",
      "Per-wire: reference-1.0.py … reference-1.4.py"
    ],
    "client_javascript": [
      "dveDeveloperHandshake(hello, prefer?) default prefer 1.4",
      "Per-wire standalone: reference-1.0.js … reference-1.4.js"
    ],
    "app_message_convention": {
      "dve_op": "string, logical RPC or message name",
      "dve_seq": "optional int, application-level seq (distinct from wire pkt_seq)"
    }
  },
  "compatibility": {
    "v1_clients": "Ignore unknown hello.offer / caps; omit select → wire 1.0.",
    "v1_3_clients": "Prefer 1.3 when offered; ignore unknown 1.4/1.5.",
    "v1_4_clients": "Prefer select 1.4 when offered; otherwise highest mutually known wire.",
    "v1_5_clients": "select 1.5 is an alias for wire 1.4 (ver=0x14). Prefer 1.5 or 1.4 interchangeably.",
    "mixed": "Server keeps mode per connection based on select.",
    "freeze": "After 1.5 LTS, Wire 1.x binary layout and ptype map are frozen. Additive reserved bits/ptypes only with a new major version."
  },
  "security_considerations": {
    "nonce_reuse": "Wire 1.3+ MUST use per-direction random 4-byte prefix + monotonic u64be counter starting at 1. Implementations MUST NOT reuse a (key, nonce) pair. Exhaustion of the 64-bit counter MUST terminate the session and force a new handshake (or Resume with fresh keys).",
    "pkt_seq": "Wire 1.4 pkt_seq is per-direction u32be starting at 1, bound into AAD. Receivers SHOULD track the highest contiguous seq per stream for DATA_ACK. Duplicate or grossly out-of-window seq MAY be dropped; gap handling is implementation-defined but MUST NOT decrypt with a reused nonce.",
    "session_ticket_lifetime": "Default TTL 86400 seconds (24h). Servers MUST reject expired tickets with dve_resume_reject. Tickets SHOULD be bound to the authenticated identity (e.g. user_hash). Stolen tickets without matching auth MUST fail. Servers MAY rotate tickets on each successful Resume.",
    "counter_overflow": "Nonce counter u64 and pkt_seq u32 overflow MUST close the session. Do not wrap. Clients reconnect with full handshake or Resume (new AEAD keys).",
    "replay": "AEAD tag failure rejects the frame. Counter nonces + AAD (header fields) limit silent replay of ciphertext under the same key. After Resume, keys change (HKDF resume labels), so prior-session ciphertext cannot decrypt. Applications SHOULD still apply app-level idempotency (dve_seq / client_msg_id) where relevant.",
    "downgrade": "Servers offering multiple wires MUST NOT accept a lower select than the client's stated maximum when the higher wire is mutually supported if policy requires it; the default algorithm is 'client select ∈ offer, else highest offered ≤ prefer'. Clients that require 1.4+ MUST abort if the negotiated wire is lower. Ignore unknown offer entries; never interpret absence of caps as enabling reserved 2.0 carriers.",
    "resume_secret": "Resume token is 32 bytes of fresh CSPRNG output. Store hashed at rest when possible; reference in-memory store keeps the opaque token and compares with constant-time equality. Do not log tokens.",
    "forward_secrecy": "Full ECDH sessions have ephemeral X25519 keys. Resume reuses the ticket secret until expiry/rotation — shorter TTL improves FS properties for resumed sessions."
  },
  "exchange_api": {
    "url": "https://api.f-chat.ru/dve_exchange.php",
    "openapi": "https://api.f-chat.ru/dve_exchange.openapi.yaml",
    "auth": "Same session cookies as feed (secure_session + auth_token).",
    "routing_modes": {
      "e2e": "Device encrypts → server stores opaque payload → recipient decrypts.",
      "gateway": "Device encrypts with server RSA public key → server may read plaintext."
    }
  },
  "machine_readable_spec_url": "https://api.f-chat.ru/DVeProto",
  "legacy_spec_url": "https://api.f-chat.ru/dveproto_spec.php",
  "test_vectors_url": "./test_vectors.json",
  "dvevpn": {
    "version": "2.0.0",
    "protocol_frozen": true,
    "forbidden": [
      "packet format changes",
      "crypto defaults changes",
      "config breaking changes"
    ],
    "freeze_document": "./DVEVPN_RC_FREEZE.md",
    "stable_checklist": "./STABLE_CHECKLIST.md",
    "release_notes": "./STABLE_RELEASE_NOTES.md",
    "phase": "2.0 Stable",
    "stable_release_check": "./STABLE_RELEASE_CHECK.md"
  }
}
