/sysdep/autoconf.h.in~
/cscope.*
*.tar.gz
+__pycache__
--- /dev/null
+import asyncio
+
+class BIRDException(Exception):
+ pass
+
+class Basic:
+ def __init__(self, bird):
+ self.bird = bird
+ self.data = None
+
+ def __getattr__(self, name):
+ if self.data is None:
+ raise BIRDException(f"Call update() to get data")
+
+ if name not in self.data:
+ raise BIRDException(f"Unknown key {name} in {type(self)}")
+
+ return self.data[name]
+
+ def __repr__(self):
+ return f"{type(self).__name__}({self.data})"
+
+ async def load(self):
+ if self.data is None:
+ await self.update()
--- /dev/null
+import asyncio
+
+class SocketException(Exception):
+ def __init__(self, socket, msg):
+ Exception.__init__(self, f"Failed to {msg} BIRD Control Socket at {socket.path}")
+
+class ReadException(Exception):
+ def __init__(self, socket, line, msg):
+ Exception.__init__(self, f"Invalid input on line {line}: {msg}")
+
+class Socket:
+ def __init__(self, path):
+ self.path = path
+ self.reader = None
+ self.writer = None
+
+ async def open(self):
+ assert(self.reader is None)
+ assert(self.writer is None)
+
+ try:
+ self.reader, self.writer = await asyncio.open_unix_connection(path=self.path)
+ except Exception as e:
+ raise SocketException(self, "connect to") from e
+
+ try:
+ return await self.read_from_socket()
+ except ReadException as e:
+ raise SocketException(self, "read hello from") from e
+
+ async def close(self):
+ assert(self.reader is not None)
+ assert(self.writer is not None)
+
+ try:
+ self.writer.close()
+ await self.writer.wait_closed()
+ except Exception as e:
+ raise SocketException(self, "close") from e
+
+ self.reader = None
+ self.writer = None
+
+ async def read_from_socket(self):
+ current_code = None
+ lines = []
+
+ while True:
+ line = (await self.reader.readline()).decode()
+
+ if len(line) == 0:
+ raise ReadException(self, len(lines)+1, "Connection closed")
+
+ if line[-1] != "\n":
+ raise ReadException(self, len(lines)+1, "Received partial data")
+
+ if line[0] == " ":
+ if current_code is None:
+ raise ReadException(self, len(lines)+1, "First line can't be unnumbered continuation")
+ lines.append({"code": current_code, "data": line[1:-1]})
+
+ elif line[4] == "-" or line[4] == " ":
+ try:
+ current_code = int(line[:4])
+ except ValueError as e:
+ raise ReadException(self, len(lines)+1, f"Invalid line code: {line[:4]}") from e
+
+ lines.append({"code": current_code, "data": line[5:-1]})
+
+ if line[4] == " ":
+ return lines
+
+ async def command(self, cmd):
+ try:
+ self.writer.write(f"{cmd}\n".encode())
+ await self.writer.drain()
+ except Exception as e:
+ raise SocketException(self, f"write command {cmd} to") from e
+
+ try:
+ return await self.read_from_socket()
+ except Exception as e:
+ raise SocketException(self, f"read response for command {cmd} from") from e
+
--- /dev/null
+import asyncio
+from BIRD.Basic import Basic
+
+class StatusException(Exception):
+ def __init__(self, msg):
+ Exception.__init__(self, "Failed to parse status: " + msg)
+
+class Status(Basic):
+ async def update(self):
+ self.data = {}
+
+ await self.bird.cli.open()
+ data = await self.bird.cli.socket.command("show status")
+
+ if data[0]["code"] != 1000:
+ raise StatusException(f"BIRD version not on the first line, got {data[0]['code']}")
+
+ self.data["version"] = data[0]["data"]
+
+ if data[-1]["code"] != 13:
+ raise StatusException(f"BIRD status not on the last line, got {data[-1]['code']}")
+
+ self.data["status"] = data[-1]["data"]
+
+# for d in data[1:-1]:
+
+
+
+class VersionException(Exception):
+ def __init__(self, msg):
+ Exception.__init__(self, "Failed to parse version from socket hello: " + msg)
+
+class Version(Basic):
+ async def update(self):
+ await self.bird.cli.open()
+ hello = self.bird.cli.hello
+
+ if hello["code"] != 1:
+ raise VersionException(f"code is {hello['code']}, should be 1")
+
+ s = hello["data"].split(" ")
+ if len(s) != 3 or s[2] != "ready.":
+ raise VersionException(f"malformed hello: {hello['data']}")
+
+ self.data = {
+ "name": s[0],
+ "version": s[1],
+ }
--- /dev/null
+import asyncio
+from pathlib import Path
+
+from BIRD.Basic import BIRDException
+from BIRD.Socket import Socket
+from BIRD.Status import Status, Version
+
+class CLI:
+ def __init__(self, name):
+ self.socket = Socket(name)
+ self.connected = False
+ self.hello = None
+
+ async def open(self):
+ if self.hello is not None:
+ return
+
+ h = await self.socket.open()
+ if len(h) != 1:
+ raise BIRDException("CLI hello should have 1 line, has {len(h)} lines: {h}")
+
+ self.hello = h[0]
+
+ async def close(self):
+ if self.hello is None:
+ return
+
+ await self.socket.close()
+ self.hello = None
+
+class BIRD:
+ def __init__(self, socket=Path("bird.ctl")):
+ self.cli = CLI(socket)
+ self.version = Version(self)
+ self.status = Status(self)
+
+ self.within = False
+
+ async def __aenter__(self):
+ if self.within:
+ raise BIRDException("Tried to enter BIRD context (async with) more than once")
+
+ self.within = True
+ return self
+
+ async def __aexit__(self, *args):
+ await self.cli.close()
+ self.within = False
--- /dev/null
+import asyncio
+from BIRD import BIRD
+
+async def main():
+ async with BIRD("/run/bird/bird.ctl") as b:
+ await b.version.update()
+ print(b.version)
+
+ await b.status.update()
+ print(b.status)
+
+asyncio.run(main())