From 6c383ce88338ce6542706b686aa86f29a006e3b6 Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Mon, 3 May 2021 22:38:52 -0700 Subject: [PATCH] add a module for doing multicast work.. --- Makefile | 4 +-- multicast.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 multicast.py diff --git a/Makefile b/Makefile index ac151c4..f855762 100644 --- a/Makefile +++ b/Makefile @@ -168,8 +168,8 @@ runbuild: $(SRCS) for i in $(.MAKEFILE_LIST) $(.ALLSRC) $$(cat $(DEPENDS) | gsed ':x; /\\$$/ { N; s/\\\n//; tx }' | sed -e 's/^[^:]*://'); do if [ "$$i" != ".." ]; then echo $$i; fi; done | entr -d sh -c 'echo starting...; cd $(.CURDIR) && $(MAKE) $(.MAKEFLAGS) depend && $(MAKE) $(.MAKEFLAGS) all' .PHONY: runtests -runtests: Makefile lora_comms.py lora.py $(LIBLORA_TEST) $(LIBLORA_TEST_SRCS) - ls $(.ALLSRC) | entr sh -c '(cd $(.CURDIR) && $(MAKE) $(.MAKEFLAGS) $(LIBLORA_TEST)) && ((PYTHONPATH="$(.CURDIR)" python -m coverage run -m unittest lora && coverage report --omit=$(.CURDIR)/p/\* -m -i) 2>&1 | head -n 30)' +runtests: Makefile lora_comms.py lora.py multicast.py $(LIBLORA_TEST) $(LIBLORA_TEST_SRCS) + ls $(.ALLSRC) | entr sh -c '(cd $(.CURDIR) && $(MAKE) $(.MAKEFLAGS) $(LIBLORA_TEST)) && ((PYTHONPATH="$(.CURDIR)" python -m coverage run -m unittest lora multicast && coverage report --omit=$(.CURDIR)/p/\* -m -i) 2>&1 | head -n 30)' # native objects .SUFFIXES: .no diff --git a/multicast.py b/multicast.py new file mode 100644 index 0000000..218d847 --- /dev/null +++ b/multicast.py @@ -0,0 +1,91 @@ +import asyncio +import random +import socket +import struct +import unittest + +from lora import timeout + +# This function based upon code from: +# https://gist.github.com/petrdvor/e802bec72e78ace061ab9d4469418fae#file-async-multicast-receiver-server-py-L54-L72 +def make_multisock(maddr): + # family, type, proto, ??, addr) + addrinfo = socket.getaddrinfo(*maddr, type=socket.SOCK_DGRAM)[0] + + sock = socket.socket(*addrinfo[:2]) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + sock.bind(maddr) + + group_bin = socket.inet_pton(addrinfo[0], addrinfo[4][0]) + mreq = group_bin + struct.pack('=I', socket.INADDR_ANY) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + + return sock + +class StupidProtocol(object): + def __init__(self): + self.transport = None + + def connection_made(self, transport): + # Note: the connection_made call seems to be sync. This + # isn't documented, and I don't know how to force a test + # if it isn't. + self.transport = transport + +class ReceiverProtocol(StupidProtocol): + def __init__(self): + super().__init__() + + self._q = asyncio.Queue() + + def datagram_received(self, data, addr): + self._q.put_nowait((data, addr)) + + async def recv(self): + return await self._q.get() + +async def create_multicast_receiver(maddr): + sock = make_multisock(maddr) + + loop = asyncio.get_running_loop() + transport, protocol = await loop.create_datagram_endpoint( + lambda: ReceiverProtocol(), + sock=sock) + + return protocol + +class TransmitterProtocol(StupidProtocol): + async def send(self, msg): + self.transport.sendto(msg) + +async def create_multicast_transmitter(maddr): + loop = asyncio.get_running_loop() + transport, protocol = await loop.create_datagram_endpoint( + lambda: TransmitterProtocol(), + remote_addr=maddr) + + return protocol + +class TestMulticast(unittest.IsolatedAsyncioTestCase): + @timeout(2) + async def test_multicast(self): + # see: https://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml#multicast-addresses-1 + maddr = ('224.0.0.%d' % random.randint(151, 250), 3485) + + l1 = await create_multicast_receiver(maddr) + l2 = await create_multicast_receiver(maddr) + + t1 = await create_multicast_transmitter(maddr) + print('tm:', repr(t1)) + + msg = b'test message' + + await t1.send(msg) + await t1.send(msg) + + self.assertEqual((await l1.recv())[0], msg) + self.assertEqual((await l2.recv())[0], msg) + + self.assertEqual((await l1.recv())[0], msg) + self.assertEqual((await l2.recv())[0], msg)