cln-plugins/backup/protocol.py

82 lines
2.2 KiB
Python
Raw Permalink Normal View History

2024-07-01 16:34:29 +02:00
"""
2020-12-30 21:58:39 +01:00
Socket-based remote backup protocol. This is used to create a connection to a backup backend, and send it incremental database updates.
2024-07-01 16:34:29 +02:00
"""
2020-12-30 21:58:39 +01:00
import socket
import struct
from typing import Tuple
import zlib
from backend import Change
2022-12-27 13:52:45 +01:00
2020-12-30 21:58:39 +01:00
class PacketType:
CHANGE = 0x01
SNAPSHOT = 0x02
REWIND = 0x03
REQ_METADATA = 0x04
RESTORE = 0x05
ACK = 0x06
NACK = 0x07
METADATA = 0x08
DONE = 0x09
2024-07-01 16:34:29 +02:00
COMPACT = 0x0A
COMPACT_RES = 0x0B
2020-12-30 21:58:39 +01:00
2022-12-27 13:52:45 +01:00
2020-12-30 21:58:39 +01:00
PKT_CHANGE_TYPES = {PacketType.CHANGE, PacketType.SNAPSHOT}
2022-12-27 13:52:45 +01:00
2020-12-30 21:58:39 +01:00
def recvall(sock: socket.socket, n: int) -> bytearray:
2024-07-01 16:34:29 +02:00
"""Receive exactly n bytes from a socket."""
2020-12-30 21:58:39 +01:00
buf = bytearray(n)
view = memoryview(buf)
ptr = 0
while ptr < n:
count = sock.recv_into(view[ptr:])
if count == 0:
2024-07-01 16:34:29 +02:00
raise IOError("Premature end of stream")
2020-12-30 21:58:39 +01:00
ptr += count
return buf
2022-12-27 13:52:45 +01:00
2020-12-30 21:58:39 +01:00
def send_packet(sock: socket.socket, typ: int, payload: bytes) -> None:
2024-07-01 16:34:29 +02:00
sock.sendall(struct.pack("!BI", typ, len(payload)))
2020-12-30 21:58:39 +01:00
sock.sendall(payload)
2022-12-27 13:52:45 +01:00
2020-12-30 21:58:39 +01:00
def recv_packet(sock: socket.socket) -> Tuple[int, bytes]:
2024-07-01 16:34:29 +02:00
(typ, length) = struct.unpack("!BI", recvall(sock, 5))
2020-12-30 21:58:39 +01:00
payload = recvall(sock, length)
return (typ, payload)
2022-12-27 13:52:45 +01:00
2020-12-30 21:58:39 +01:00
def change_from_packet(typ, payload):
2024-07-01 16:34:29 +02:00
"""Convert a network packet to a Change object."""
2020-12-30 21:58:39 +01:00
if typ == PacketType.CHANGE:
2024-07-01 16:34:29 +02:00
(version,) = struct.unpack("!I", payload[0:4])
2020-12-30 21:58:39 +01:00
payload = zlib.decompress(payload[4:])
2024-07-01 16:34:29 +02:00
return Change(
version=version,
snapshot=None,
transaction=[t.decode("UTF-8") for t in payload.split(b"\x00")],
)
2020-12-30 21:58:39 +01:00
elif typ == PacketType.SNAPSHOT:
2024-07-01 16:34:29 +02:00
(version,) = struct.unpack("!I", payload[0:4])
2020-12-30 21:58:39 +01:00
payload = zlib.decompress(payload[4:])
return Change(version=version, snapshot=payload, transaction=None)
2024-07-01 16:34:29 +02:00
raise ValueError("Not a change (typ {})".format(typ))
2020-12-30 21:58:39 +01:00
2022-12-27 13:52:45 +01:00
2020-12-30 21:58:39 +01:00
def packet_from_change(entry):
2024-07-01 16:34:29 +02:00
"""Convert a Change object to a network packet."""
2020-12-30 21:58:39 +01:00
if entry.snapshot is None:
typ = PacketType.CHANGE
2024-07-01 16:34:29 +02:00
payload = b"\x00".join([t.encode("UTF-8") for t in entry.transaction])
2020-12-30 21:58:39 +01:00
else:
typ = PacketType.SNAPSHOT
payload = entry.snapshot
version = struct.pack("!I", entry.version)
return typ, version + zlib.compress(payload)