153e354dd0edd0d111fb7512f366c4764ffc2e681Phil#! /usr/bin/env python
253e354dd0edd0d111fb7512f366c4764ffc2e681Phil
353e354dd0edd0d111fb7512f366c4764ffc2e681Phil## This file is part of Scapy
453e354dd0edd0d111fb7512f366c4764ffc2e681Phil## See http://www.secdev.org/projects/scapy for more informations
553e354dd0edd0d111fb7512f366c4764ffc2e681Phil## Copyright (C) Philippe Biondi <phil@secdev.org>
653e354dd0edd0d111fb7512f366c4764ffc2e681Phil## This program is published under a GPLv2 license
753e354dd0edd0d111fb7512f366c4764ffc2e681Phil
8b72a0b59db7b6fa0726ffa22da3b88679bffe69cgpotterfrom __future__ import print_function
959a7b9f69b5b745e58c01288cb4f85fb535250b0gpotterimport os
1053e354dd0edd0d111fb7512f366c4764ffc2e681Philimport subprocess
1153e354dd0edd0d111fb7512f366c4764ffc2e681Philimport itertools
1253e354dd0edd0d111fb7512f366c4764ffc2e681Philimport collections
1353e354dd0edd0d111fb7512f366c4764ffc2e681Philimport time
1422a55b62eb35e8611fe03b99e4ff4f257a97b5d1gpotterimport scapy.modules.six as six
1559a7b9f69b5b745e58c01288cb4f85fb535250b0gpotterfrom threading import Lock, Thread
1622a55b62eb35e8611fe03b99e4ff4f257a97b5d1gpotterimport scapy.utils
176057906368d55634d11e1d19a5cca1f127595b11Robin Jarry
180bfed5c5783d9e883abd6055079c833340052616gpotterfrom scapy.automaton import Message, select_objects, SelectableObject
19c8aedce97942cf1963c137902d50d14cd5c28bf0gpotterfrom scapy.consts import WINDOWS
202a54fe68cadcb2b75c9c31244499c2b21f385eb4gpotterfrom scapy.error import log_interactive, warning
216057906368d55634d11e1d19a5cca1f127595b11Robin Jarryfrom scapy.config import conf
22c8aedce97942cf1963c137902d50d14cd5c28bf0gpotterfrom scapy.utils import get_temp_file, do_graph
23c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter
24c8aedce97942cf1963c137902d50d14cd5c28bf0gpotterimport scapy.arch
2553e354dd0edd0d111fb7512f366c4764ffc2e681Phil
260bfed5c5783d9e883abd6055079c833340052616gpotterclass PipeEngine(SelectableObject):
2753e354dd0edd0d111fb7512f366c4764ffc2e681Phil    pipes = {}
2853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    @classmethod
2953e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def list_pipes(cls):
3053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        for pn,pc in sorted(cls.pipes.items()):
3153e354dd0edd0d111fb7512f366c4764ffc2e681Phil            doc = pc.__doc__ or ""
3253e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if doc:
3353e354dd0edd0d111fb7512f366c4764ffc2e681Phil                doc = doc.splitlines()[0]
34b72a0b59db7b6fa0726ffa22da3b88679bffe69cgpotter            print("%20s: %s" % (pn, doc))
3553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    @classmethod
3653e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def list_pipes_detailed(cls):
3753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        for pn,pc in sorted(cls.pipes.items()):
3853e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if pc.__doc__:
39b72a0b59db7b6fa0726ffa22da3b88679bffe69cgpotter                print("###### %s\n %s" % (pn ,pc.__doc__))
4053e354dd0edd0d111fb7512f366c4764ffc2e681Phil            else:
41b72a0b59db7b6fa0726ffa22da3b88679bffe69cgpotter                print("###### %s" % pn)
4253e354dd0edd0d111fb7512f366c4764ffc2e681Phil
4353e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, *pipes):
4453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.active_pipes = set()
4553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.active_sources = set()
4653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.active_drains = set()
4753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.active_sinks = set()
4853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._add_pipes(*pipes)
4959a7b9f69b5b745e58c01288cb4f85fb535250b0gpotter        self.thread_lock = Lock()
5059a7b9f69b5b745e58c01288cb4f85fb535250b0gpotter        self.command_lock = Lock()
510bfed5c5783d9e883abd6055079c833340052616gpotter        self.__fd_queue = collections.deque()
5253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.__fdr,self.__fdw = os.pipe()
530bfed5c5783d9e883abd6055079c833340052616gpotter        self.thread = None
5453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __getattr__(self, attr):
5553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if attr.startswith("spawn_"):
5653e354dd0edd0d111fb7512f366c4764ffc2e681Phil            dname = attr[6:]
5753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if dname in self.pipes:
5853e354dd0edd0d111fb7512f366c4764ffc2e681Phil                def f(*args, **kargs):
5953e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    k = self.pipes[dname]
6053e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    p = k(*args, **kargs)
6153e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    self.add(p)
6253e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    return p
6353e354dd0edd0d111fb7512f366c4764ffc2e681Phil                return f
6453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        raise AttributeError(attr)
6553e354dd0edd0d111fb7512f366c4764ffc2e681Phil
660bfed5c5783d9e883abd6055079c833340052616gpotter    def check_recv(self):
67c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter        """As select.select is not available, we check if there
68c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter        is some data to read by using a list that stores pointers."""
69c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter        return len(self.__fd_queue) > 0
70c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter
71c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter    def fileno(self):
72c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter        return self.__fdr
73c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter
74c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter    def _read_cmd(self):
750bfed5c5783d9e883abd6055079c833340052616gpotter        os.read(self.__fdr,1)
760bfed5c5783d9e883abd6055079c833340052616gpotter        return self.__fd_queue.popleft()
77c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter
78c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter    def _write_cmd(self, _cmd):
790bfed5c5783d9e883abd6055079c833340052616gpotter        self.__fd_queue.append(_cmd)
801fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter        os.write(self.__fdw, b"X")
810bfed5c5783d9e883abd6055079c833340052616gpotter        self.call_release()
82c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter
8353e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def add_one_pipe(self, pipe):
8453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.active_pipes.add(pipe)
8553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if isinstance(pipe, Source):
8653e354dd0edd0d111fb7512f366c4764ffc2e681Phil            self.active_sources.add(pipe)
8753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if isinstance(pipe, Drain):
8853e354dd0edd0d111fb7512f366c4764ffc2e681Phil            self.active_drains.add(pipe)
8953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if isinstance(pipe, Sink):
9053e354dd0edd0d111fb7512f366c4764ffc2e681Phil            self.active_sinks.add(pipe)
9153e354dd0edd0d111fb7512f366c4764ffc2e681Phil
9253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def get_pipe_list(self, pipe):
9353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        def flatten(p, l):
9453e354dd0edd0d111fb7512f366c4764ffc2e681Phil            l.add(p)
9553e354dd0edd0d111fb7512f366c4764ffc2e681Phil            for q in p.sources|p.sinks|p.high_sources|p.high_sinks:
9653e354dd0edd0d111fb7512f366c4764ffc2e681Phil                if q not in l:
9753e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    flatten(q, l)
98c277f8948afe9d19703168fca385d2fa6df67657Phil        pl = set()
9953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        flatten(pipe, pl)
10053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return pl
10153e354dd0edd0d111fb7512f366c4764ffc2e681Phil
10253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def _add_pipes(self, *pipes):
103c277f8948afe9d19703168fca385d2fa6df67657Phil        pl = set()
10453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        for p in pipes:
105c277f8948afe9d19703168fca385d2fa6df67657Phil            pl |= self.get_pipe_list(p)
106c277f8948afe9d19703168fca385d2fa6df67657Phil        pl -= self.active_pipes
107c277f8948afe9d19703168fca385d2fa6df67657Phil        for q in pl:
108c277f8948afe9d19703168fca385d2fa6df67657Phil            self.add_one_pipe(q)
109c277f8948afe9d19703168fca385d2fa6df67657Phil        return pl
11053e354dd0edd0d111fb7512f366c4764ffc2e681Phil
11153e354dd0edd0d111fb7512f366c4764ffc2e681Phil
11253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def run(self):
11353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        log_interactive.info("Pipe engine thread started.")
11453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        try:
11553e354dd0edd0d111fb7512f366c4764ffc2e681Phil            for p in self.active_pipes:
11653e354dd0edd0d111fb7512f366c4764ffc2e681Phil                p.start()
11753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            sources = self.active_sources
118c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter            sources.add(self)
11953e354dd0edd0d111fb7512f366c4764ffc2e681Phil            exhausted = set([])
12053e354dd0edd0d111fb7512f366c4764ffc2e681Phil            RUN=True
12153e354dd0edd0d111fb7512f366c4764ffc2e681Phil            STOP_IF_EXHAUSTED = False
12253e354dd0edd0d111fb7512f366c4764ffc2e681Phil            while RUN and (not STOP_IF_EXHAUSTED or len(sources) > 1):
1230bfed5c5783d9e883abd6055079c833340052616gpotter                fds = select_objects(sources, 2)
12453e354dd0edd0d111fb7512f366c4764ffc2e681Phil                for fd in fds:
125c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter                    if fd is self:
126c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter                        cmd = self._read_cmd()
12753e354dd0edd0d111fb7512f366c4764ffc2e681Phil                        if cmd == "X":
12853e354dd0edd0d111fb7512f366c4764ffc2e681Phil                            RUN=False
12953e354dd0edd0d111fb7512f366c4764ffc2e681Phil                            break
13053e354dd0edd0d111fb7512f366c4764ffc2e681Phil                        elif cmd == "B":
13153e354dd0edd0d111fb7512f366c4764ffc2e681Phil                            STOP_IF_EXHAUSTED = True
13253e354dd0edd0d111fb7512f366c4764ffc2e681Phil                        elif cmd == "A":
13353e354dd0edd0d111fb7512f366c4764ffc2e681Phil                            sources = self.active_sources-exhausted
13437b8c8237ab0fcca93c09a9c8094501cf0591f8egpotter                            sources.add(self)
13553e354dd0edd0d111fb7512f366c4764ffc2e681Phil                        else:
13653e354dd0edd0d111fb7512f366c4764ffc2e681Phil                            warning("Unknown internal pipe engine command: %r. Ignoring." % cmd)
13753e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    elif fd in sources:
13853e354dd0edd0d111fb7512f366c4764ffc2e681Phil                        try:
13953e354dd0edd0d111fb7512f366c4764ffc2e681Phil                            fd.deliver()
140d51edef8530fe1e944f13eb65ef863c2d7f04b1dgpotter                        except Exception as e:
14153e354dd0edd0d111fb7512f366c4764ffc2e681Phil                            log_interactive.exception("piping from %s failed: %s" % (fd.name, e))
14253e354dd0edd0d111fb7512f366c4764ffc2e681Phil                        else:
14353e354dd0edd0d111fb7512f366c4764ffc2e681Phil                            if fd.exhausted():
14453e354dd0edd0d111fb7512f366c4764ffc2e681Phil                                exhausted.add(fd)
14553e354dd0edd0d111fb7512f366c4764ffc2e681Phil                                sources.remove(fd)
14653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        except KeyboardInterrupt:
14753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            pass
14853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        finally:
14953e354dd0edd0d111fb7512f366c4764ffc2e681Phil            try:
15053e354dd0edd0d111fb7512f366c4764ffc2e681Phil                for p in self.active_pipes:
15153e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    p.stop()
15253e354dd0edd0d111fb7512f366c4764ffc2e681Phil            finally:
15353e354dd0edd0d111fb7512f366c4764ffc2e681Phil                self.thread_lock.release()
15453e354dd0edd0d111fb7512f366c4764ffc2e681Phil                log_interactive.info("Pipe engine thread stopped.")
15553e354dd0edd0d111fb7512f366c4764ffc2e681Phil
15653e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def start(self):
15753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if self.thread_lock.acquire(0):
15859a7b9f69b5b745e58c01288cb4f85fb535250b0gpotter            _t = Thread(target=self.run)
1596e173e9f6e570d6c3c8c76040b44e270c47308dbgpotter            _t.setDaemon(True)
16059a7b9f69b5b745e58c01288cb4f85fb535250b0gpotter            _t.start()
1610bfed5c5783d9e883abd6055079c833340052616gpotter            self.thread = _t
16253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        else:
16353e354dd0edd0d111fb7512f366c4764ffc2e681Phil            warning("Pipe engine already running")
16453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def wait_and_stop(self):
16553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.stop(_cmd="B")
16653e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def stop(self, _cmd="X"):
16753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        try:
16853e354dd0edd0d111fb7512f366c4764ffc2e681Phil            with self.command_lock:
1690bfed5c5783d9e883abd6055079c833340052616gpotter                if self.thread is not None:
170c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter                    self._write_cmd(_cmd)
1710bfed5c5783d9e883abd6055079c833340052616gpotter                    self.thread.join()
1720bfed5c5783d9e883abd6055079c833340052616gpotter                    try:
1730bfed5c5783d9e883abd6055079c833340052616gpotter                        self.thread_lock.release()
1740bfed5c5783d9e883abd6055079c833340052616gpotter                    except:
1750bfed5c5783d9e883abd6055079c833340052616gpotter                        pass
17653e354dd0edd0d111fb7512f366c4764ffc2e681Phil                else:
17753e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    warning("Pipe engine thread not running")
17853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        except KeyboardInterrupt:
179b72a0b59db7b6fa0726ffa22da3b88679bffe69cgpotter            print("Interrupted by user.")
18053e354dd0edd0d111fb7512f366c4764ffc2e681Phil
18153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def add(self, *pipes):
182c277f8948afe9d19703168fca385d2fa6df67657Phil        pipes = self._add_pipes(*pipes)
18353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        with self.command_lock:
1840bfed5c5783d9e883abd6055079c833340052616gpotter            if self.thread is not None:
18553e354dd0edd0d111fb7512f366c4764ffc2e681Phil                for p in pipes:
18653e354dd0edd0d111fb7512f366c4764ffc2e681Phil                    p.start()
187c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter                self._write_cmd("A")
18853e354dd0edd0d111fb7512f366c4764ffc2e681Phil
18953e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def graph(self,**kargs):
19053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        g=['digraph "pipe" {',"\tnode [shape=rectangle];",]
19153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        for p in self.active_pipes:
19253e354dd0edd0d111fb7512f366c4764ffc2e681Phil            g.append('\t"%i" [label="%s"];' % (id(p), p.name))
19353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        g.append("")
19453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        g.append("\tedge [color=blue, arrowhead=vee];")
19553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        for p in self.active_pipes:
19653e354dd0edd0d111fb7512f366c4764ffc2e681Phil            for q in p.sinks:
19753e354dd0edd0d111fb7512f366c4764ffc2e681Phil                g.append('\t"%i" -> "%i";' % (id(p), id(q)))
19853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        g.append("")
199dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        g.append("\tedge [color=purple, arrowhead=veevee];")
20053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        for p in self.active_pipes:
20153e354dd0edd0d111fb7512f366c4764ffc2e681Phil            for q in p.high_sinks:
202dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil                g.append('\t"%i" -> "%i";' % (id(p), id(q)))
203dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        g.append("")
204dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        g.append("\tedge [color=red, arrowhead=diamond];")
205dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        for p in self.active_pipes:
206dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil            for q in p.trigger_sinks:
207dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil                g.append('\t"%i" -> "%i";' % (id(p), id(q)))
20853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        g.append('}')
20953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        graph = "\n".join(g)
210c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter        do_graph(graph, **kargs)
21153e354dd0edd0d111fb7512f366c4764ffc2e681Phil
21253e354dd0edd0d111fb7512f366c4764ffc2e681Phil
21353e354dd0edd0d111fb7512f366c4764ffc2e681Philclass _ConnectorLogic(object):
21453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self):
21553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.sources = set()
21653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.sinks = set()
21753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.high_sources = set()
21853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.high_sinks = set()
219dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        self.trigger_sources = set()
220dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        self.trigger_sinks = set()
22153e354dd0edd0d111fb7512f366c4764ffc2e681Phil
22253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __lt__(self, other):
22353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        other.sinks.add(self)
22453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.sources.add(other)
22553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return other
22653e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __gt__(self, other):
22753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.sinks.add(other)
22853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        other.sources.add(self)
22953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return other
23053e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __eq__(self, other):
23153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self > other
23253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        other > self
23353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return other
23453e354dd0edd0d111fb7512f366c4764ffc2e681Phil
23553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __lshift__(self, other):
23653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.high_sources.add(other)
23753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        other.high_sinks.add(self)
23853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return other
23953e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __rshift__(self, other):
24053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.high_sinks.add(other)
24153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        other.high_sources.add(self)
24253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return other
24353e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __floordiv__(self, other):
24453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self >> other
24553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        other >> self
24653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return other
24753e354dd0edd0d111fb7512f366c4764ffc2e681Phil
248dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil    def __xor__(self, other):
249dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        self.trigger_sinks.add(other)
250dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        other.trigger_sources.add(self)
251dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        return other
25253e354dd0edd0d111fb7512f366c4764ffc2e681Phil
2531fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter    def __hash__(self):
2541fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter        return object.__hash__(self)
2551fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter
2561fd5116590e760b8cd43ce41b8aaedec25251bcbgpotterclass _PipeMeta(type):
2571fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter    def __new__(cls, name, bases, dct):
2581fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter        c = type.__new__(cls, name, bases, dct)
2591fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter        PipeEngine.pipes[name] = c
2601fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter        return c
2611fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter
2621fd5116590e760b8cd43ce41b8aaedec25251bcbgpotterclass Pipe(six.with_metaclass(_PipeMeta, _ConnectorLogic)):
26353e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, name=None):
26453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        _ConnectorLogic.__init__(self)
26553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if name is None:
26653e354dd0edd0d111fb7512f366c4764ffc2e681Phil            name = "%s" % (self.__class__.__name__)
26753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.name = name
26853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def _send(self, msg):
26953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        for s in self.sinks:
27053e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s.push(msg)
27153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def _high_send(self, msg):
27253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        for s in self.high_sinks:
27353e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s.high_push(msg)
274f1288d41516059cab24e840f0d8cd2d1bf7735e0phil    def _trigger(self, msg=None):
275dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        for s in self.trigger_sinks:
276dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil            s.on_trigger(msg)
27753e354dd0edd0d111fb7512f366c4764ffc2e681Phil
27853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __repr__(self):
27953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        ct = conf.color_theme
28053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        s = "%s%s" % (ct.punct("<"), ct.layer_name(self.name))
28153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if self.sources or self.sinks:
28253e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s+= " %s" % ct.punct("[")
28353e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if self.sources:
28453e354dd0edd0d111fb7512f366c4764ffc2e681Phil                s+="%s%s" %  (ct.punct(",").join(ct.field_name(s.name) for s in self.sources),
28553e354dd0edd0d111fb7512f366c4764ffc2e681Phil                              ct.field_value(">"))
28653e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s += ct.layer_name("#")
28753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if self.sinks:
28853e354dd0edd0d111fb7512f366c4764ffc2e681Phil                s+="%s%s" % (ct.field_value(">"),
28953e354dd0edd0d111fb7512f366c4764ffc2e681Phil                             ct.punct(",").join(ct.field_name(s.name) for s in self.sinks))
29053e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s += ct.punct("]")
29153e354dd0edd0d111fb7512f366c4764ffc2e681Phil
29253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if self.high_sources or self.high_sinks:
29353e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s+= " %s" % ct.punct("[")
29453e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if self.high_sources:
29553e354dd0edd0d111fb7512f366c4764ffc2e681Phil                s+="%s%s" %  (ct.punct(",").join(ct.field_name(s.name) for s in self.high_sources),
29653e354dd0edd0d111fb7512f366c4764ffc2e681Phil                              ct.field_value(">>"))
29753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s += ct.layer_name("#")
29853e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if self.high_sinks:
29953e354dd0edd0d111fb7512f366c4764ffc2e681Phil                s+="%s%s" % (ct.field_value(">>"),
30053e354dd0edd0d111fb7512f366c4764ffc2e681Phil                             ct.punct(",").join(ct.field_name(s.name) for s in self.high_sinks))
30153e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s += ct.punct("]")
30253e354dd0edd0d111fb7512f366c4764ffc2e681Phil
303dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil        if self.trigger_sources or self.trigger_sinks:
304dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil            s+= " %s" % ct.punct("[")
305dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil            if self.trigger_sources:
306dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil                s+="%s%s" %  (ct.punct(",").join(ct.field_name(s.name) for s in self.trigger_sources),
307dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil                              ct.field_value("^"))
308dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil            s += ct.layer_name("#")
309dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil            if self.trigger_sinks:
310dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil                s+="%s%s" % (ct.field_value("^"),
311dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil                             ct.punct(",").join(ct.field_name(s.name) for s in self.trigger_sinks))
312dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil            s += ct.punct("]")
313dbabf910cf2ddd7e420190267dd0c2a7abc2dfb1phil
31453e354dd0edd0d111fb7512f366c4764ffc2e681Phil
31553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        s += ct.punct(">")
31653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return s
31753e354dd0edd0d111fb7512f366c4764ffc2e681Phil
3180bfed5c5783d9e883abd6055079c833340052616gpotterclass Source(Pipe, SelectableObject):
31953e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, name=None):
32053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        Pipe.__init__(self, name=name)
32153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.is_exhausted = False
32253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def _read_message(self):
32353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return Message()
32453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def deliver(self):
32553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        msg = self._read_message
32653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._send(msg)
32753e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def fileno(self):
32853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return None
3290bfed5c5783d9e883abd6055079c833340052616gpotter    def check_recv(self):
3306178b488430ef900b56f3d5c1c9e5d92c0235c4fgpotter        return False
33153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def exhausted(self):
33253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return self.is_exhausted
33353e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def start(self):
33453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
33553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def stop(self):
33653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
33753e354dd0edd0d111fb7512f366c4764ffc2e681Phil
33853e354dd0edd0d111fb7512f366c4764ffc2e681Philclass Drain(Pipe):
33953e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Repeat messages from low/high entries to (resp.) low/high exits
34053e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
34153e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|-------|->>
34253e354dd0edd0d111fb7512f366c4764ffc2e681Phil     |       |
34353e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|-------|->
34453e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
34553e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
34653e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
34753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._send(msg)
34853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
34953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._high_send(msg)
35053e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def start(self):
35153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
35253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def stop(self):
35353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
35453e354dd0edd0d111fb7512f366c4764ffc2e681Phil
35553e354dd0edd0d111fb7512f366c4764ffc2e681Philclass Sink(Pipe):
35653e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
35753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
35853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
35953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
36053e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def start(self):
36153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
36253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def stop(self):
36353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
36453e354dd0edd0d111fb7512f366c4764ffc2e681Phil
36553e354dd0edd0d111fb7512f366c4764ffc2e681Phil
3660bfed5c5783d9e883abd6055079c833340052616gpotterclass AutoSource(Source, SelectableObject):
36753e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, name=None):
36853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        Source.__init__(self, name=name)
36953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.__fdr,self.__fdw = os.pipe()
37053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._queue = collections.deque()
37153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def fileno(self):
37253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        return self.__fdr
3730bfed5c5783d9e883abd6055079c833340052616gpotter    def check_recv(self):
374c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter        return len(self._queue) > 0
37553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def _gen_data(self, msg):
37653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._queue.append((msg,False))
37753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._wake_up()
37853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def _gen_high_data(self, msg):
37953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._queue.append((msg,True))
38053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._wake_up()
38153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def _wake_up(self):
3821fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter        os.write(self.__fdw, b"X")
3836e173e9f6e570d6c3c8c76040b44e270c47308dbgpotter        self.call_release()
38453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def deliver(self):
38553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        os.read(self.__fdr,1)
38653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        try:
38753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            msg,high = self._queue.popleft()
38853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        except IndexError: #empty queue. Exhausted source
38953e354dd0edd0d111fb7512f366c4764ffc2e681Phil            pass
39053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        else:
39153e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if high:
39253e354dd0edd0d111fb7512f366c4764ffc2e681Phil                self._high_send(msg)
39353e354dd0edd0d111fb7512f366c4764ffc2e681Phil            else:
39453e354dd0edd0d111fb7512f366c4764ffc2e681Phil                self._send(msg)
39553e354dd0edd0d111fb7512f366c4764ffc2e681Phil
39653e354dd0edd0d111fb7512f366c4764ffc2e681Philclass ThreadGenSource(AutoSource):
39753e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, name=None):
39853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        AutoSource.__init__(self, name=name)
39953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.RUN = False
40053e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def generate(self):
40153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
40253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def start(self):
40353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.RUN = True
40459a7b9f69b5b745e58c01288cb4f85fb535250b0gpotter        Thread(target=self.generate).start()
40553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def stop(self):
40653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.RUN = False
40753e354dd0edd0d111fb7512f366c4764ffc2e681Phil
40853e354dd0edd0d111fb7512f366c4764ffc2e681Phil
40953e354dd0edd0d111fb7512f366c4764ffc2e681Phil
41053e354dd0edd0d111fb7512f366c4764ffc2e681Philclass ConsoleSink(Sink):
41153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Print messages on low and high entries
41253e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
41353e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|--.    |->>
41453e354dd0edd0d111fb7512f366c4764ffc2e681Phil     | print |
41553e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|--'    |->
41653e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
41753e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
41853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
419b72a0b59db7b6fa0726ffa22da3b88679bffe69cgpotter        print(">%r" % msg)
42053e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
421b72a0b59db7b6fa0726ffa22da3b88679bffe69cgpotter        print(">>%r" % msg)
42253e354dd0edd0d111fb7512f366c4764ffc2e681Phil
42353e354dd0edd0d111fb7512f366c4764ffc2e681Philclass RawConsoleSink(Sink):
42453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Print messages on low and high entries
42553e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
42653e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|--.    |->>
42753e354dd0edd0d111fb7512f366c4764ffc2e681Phil     | write |
42853e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|--'    |->
42953e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
43053e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
43153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, name=None, newlines=True):
43253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        Sink.__init__(self, name=name)
43353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.newlines = newlines
4346178b488430ef900b56f3d5c1c9e5d92c0235c4fgpotter        self._write_pipe = 1
43553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
43653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if self.newlines:
43753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            msg += "\n"
4385e8857410015a93f6371459b2f870432ded39b9fgpotter        os.write(self._write_pipe, msg.encode("utf8"))
43953e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
44053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if self.newlines:
44153e354dd0edd0d111fb7512f366c4764ffc2e681Phil            msg += "\n"
4425e8857410015a93f6371459b2f870432ded39b9fgpotter        os.write(self._write_pipe, msg.encode("utf8"))
44353e354dd0edd0d111fb7512f366c4764ffc2e681Phil
44453e354dd0edd0d111fb7512f366c4764ffc2e681Philclass CLIFeeder(AutoSource):
44553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Send messages from python command line
44653e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +--------+
44753e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|        |->>
44853e354dd0edd0d111fb7512f366c4764ffc2e681Phil     | send() |
44953e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|   `----|->
45053e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +--------+
45153e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
45253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def send(self, msg):
45353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._gen_data(msg)
45453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def close(self):
45553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.is_exhausted = True
45653e354dd0edd0d111fb7512f366c4764ffc2e681Phil
45753e354dd0edd0d111fb7512f366c4764ffc2e681Philclass CLIHighFeeder(CLIFeeder):
45853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Send messages from python command line to high output
45953e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +--------+
46053e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|   .----|->>
46153e354dd0edd0d111fb7512f366c4764ffc2e681Phil     | send() |
46253e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|        |->
46353e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +--------+
46453e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
46553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def send(self, msg):
46653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._gen_high_data(msg)
46753e354dd0edd0d111fb7512f366c4764ffc2e681Phil
46853e354dd0edd0d111fb7512f366c4764ffc2e681Phil
46953e354dd0edd0d111fb7512f366c4764ffc2e681Philclass PeriodicSource(ThreadGenSource):
47053e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Generage messages periodically on low exit
47153e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
47253e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|       |->>
47353e354dd0edd0d111fb7512f366c4764ffc2e681Phil     | msg,T |
47453e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|  `----|->
47553e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
47653e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
47753e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, msg, period, period2=0, name=None):
47853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        ThreadGenSource.__init__(self,name=name)
4791fd5116590e760b8cd43ce41b8aaedec25251bcbgpotter        if not isinstance(msg, (list, set, tuple)):
48053e354dd0edd0d111fb7512f366c4764ffc2e681Phil            msg=[msg]
48153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.msg = msg
48253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.period = period
48353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.period2 = period2
48453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def generate(self):
48553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        while self.RUN:
48653e354dd0edd0d111fb7512f366c4764ffc2e681Phil            empty_gen = True
48753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            for m in self.msg:
48853e354dd0edd0d111fb7512f366c4764ffc2e681Phil                empty_gen = False
48953e354dd0edd0d111fb7512f366c4764ffc2e681Phil                self._gen_data(m)
49053e354dd0edd0d111fb7512f366c4764ffc2e681Phil                time.sleep(self.period)
49153e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if empty_gen:
49253e354dd0edd0d111fb7512f366c4764ffc2e681Phil                self.is_exhausted = True
49353e354dd0edd0d111fb7512f366c4764ffc2e681Phil                self._wake_up()
49453e354dd0edd0d111fb7512f366c4764ffc2e681Phil            time.sleep(self.period2)
49553e354dd0edd0d111fb7512f366c4764ffc2e681Phil
49653e354dd0edd0d111fb7512f366c4764ffc2e681Philclass TermSink(Sink):
49753e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Print messages on low and high entries on a separate terminal
49853e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
49953e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|--.    |->>
50053e354dd0edd0d111fb7512f366c4764ffc2e681Phil     | print |
50153e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|--'    |->
50253e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
50353e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
50453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, name=None, keepterm=True, newlines=True, openearly=True):
50553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        Sink.__init__(self, name=name)
50653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.keepterm = keepterm
50753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.newlines = newlines
50853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.openearly = openearly
50953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.opened = False
51053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if self.openearly:
51153e354dd0edd0d111fb7512f366c4764ffc2e681Phil            self.start()
512c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter    def _start_windows(self):
51353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if not self.opened:
51453e354dd0edd0d111fb7512f366c4764ffc2e681Phil            self.opened = True
515c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            self.__f = get_temp_file()
5160bfed5c5783d9e883abd6055079c833340052616gpotter            open(self.__f, "a").close()
517c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            self.name = "Scapy" if self.name is None else self.name
518c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            # Start a powershell in a new window and print the PID
519c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            cmd = "$app = Start-Process PowerShell -ArgumentList '-command &{$host.ui.RawUI.WindowTitle=\\\"%s\\\";Get-Content \\\"%s\\\" -wait}' -passthru; echo $app.Id" % (self.name, self.__f.replace("\\", "\\\\"))
520beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            proc = subprocess.Popen([conf.prog.powershell, cmd], stdout=subprocess.PIPE)
521beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            output, _ = proc.communicate()
522c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            # This is the process PID
523beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            self.pid = int(output)
524beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            print("PID: %d" % self.pid)
525c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter    def _start_unix(self):
52653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if not self.opened:
52753e354dd0edd0d111fb7512f366c4764ffc2e681Phil            self.opened = True
528beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            rdesc, self.wdesc = os.pipe()
52953e354dd0edd0d111fb7512f366c4764ffc2e681Phil            cmd = ["xterm"]
53053e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if self.name is not None:
53153e354dd0edd0d111fb7512f366c4764ffc2e681Phil                cmd.extend(["-title",self.name])
53253e354dd0edd0d111fb7512f366c4764ffc2e681Phil            if self.keepterm:
53353e354dd0edd0d111fb7512f366c4764ffc2e681Phil                cmd.append("-hold")
534beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            cmd.extend(["-e", "cat <&%d" % rdesc])
535beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            self.proc = subprocess.Popen(cmd, close_fds=False)
536beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            os.close(rdesc)
537c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter    def start(self):
538c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter        if WINDOWS:
539c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            return self._start_windows()
540c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter        else:
541c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            return self._start_unix()
542c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter    def _stop_windows(self):
54353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if not self.keepterm:
54453e354dd0edd0d111fb7512f366c4764ffc2e681Phil            self.opened = False
545c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            # Recipe to kill process with PID
546c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            # http://code.activestate.com/recipes/347462-terminating-a-subprocess-on-windows/
547c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            import ctypes
548c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            PROCESS_TERMINATE = 1
549beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.pid)
550c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            ctypes.windll.kernel32.TerminateProcess(handle, -1)
551c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            ctypes.windll.kernel32.CloseHandle(handle)
552c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter    def _stop_unix(self):
55353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if not self.keepterm:
55453e354dd0edd0d111fb7512f366c4764ffc2e681Phil            self.opened = False
555beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            self.proc.kill()
556beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            self.proc.wait()
557c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter    def stop(self):
558c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter        if WINDOWS:
559c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            return self._stop_windows()
560c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter        else:
561c41c2867ee5d7d64bb58d159ab97e638f3cb96c3gpotter            return self._stop_unix()
56253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def _print(self, s):
56353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        if self.newlines:
56453e354dd0edd0d111fb7512f366c4764ffc2e681Phil            s+="\n"
565c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter        if WINDOWS:
566beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            wdesc = open(self.__f, "a")
567beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            wdesc.write(s)
568beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            wdesc.close()
569c8aedce97942cf1963c137902d50d14cd5c28bf0gpotter        else:
570beb5bd8ebcac8c800ffdcd379804740ab448b0ecPierre LALET            os.write(self.wdesc, s.encode())
57153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
57253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._print(str(msg))
57353e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
57453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._print(str(msg))
57553e354dd0edd0d111fb7512f366c4764ffc2e681Phil
57653e354dd0edd0d111fb7512f366c4764ffc2e681Phil
57753e354dd0edd0d111fb7512f366c4764ffc2e681Philclass QueueSink(Sink):
57853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Collect messages from high and low entries and queue them. Messages are unqueued with the .recv() method.
57953e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
58053e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|--.    |->>
58153e354dd0edd0d111fb7512f366c4764ffc2e681Phil     | queue |
58253e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|--'    |->
58353e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
58453e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
58553e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, name=None):
58653e354dd0edd0d111fb7512f366c4764ffc2e681Phil        Sink.__init__(self, name=name)
58722a55b62eb35e8611fe03b99e4ff4f257a97b5d1gpotter        self.q = six.moves.queue.Queue()
58853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
58953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.q.put(msg)
59053e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
59153e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.q.put(msg)
59253e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def recv(self):
59353e354dd0edd0d111fb7512f366c4764ffc2e681Phil        while True:
59453e354dd0edd0d111fb7512f366c4764ffc2e681Phil            try:
59553e354dd0edd0d111fb7512f366c4764ffc2e681Phil                return self.q.get(True, timeout=0.1)
59622a55b62eb35e8611fe03b99e4ff4f257a97b5d1gpotter            except six.moves.queue.Empty:
59753e354dd0edd0d111fb7512f366c4764ffc2e681Phil                pass
59853e354dd0edd0d111fb7512f366c4764ffc2e681Phil
59953e354dd0edd0d111fb7512f366c4764ffc2e681Phil
60053e354dd0edd0d111fb7512f366c4764ffc2e681Philclass TransformDrain(Drain):
60153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Apply a function to messages on low and high entry
60253e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
60353e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|--[f]--|->>
60453e354dd0edd0d111fb7512f366c4764ffc2e681Phil     |       |
60553e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|--[f]--|->
60653e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
60753e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
60853e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def __init__(self, f, name=None):
60953e354dd0edd0d111fb7512f366c4764ffc2e681Phil        Drain.__init__(self, name=name)
61053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self.f = f
61153e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
61253e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._send(self.f(msg))
61353e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
61453e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._high_send(self.f(msg))
61553e354dd0edd0d111fb7512f366c4764ffc2e681Phil
61653e354dd0edd0d111fb7512f366c4764ffc2e681Philclass UpDrain(Drain):
61753e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Repeat messages from low entry to high exit
61853e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
61953e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|    ,--|->>
62053e354dd0edd0d111fb7512f366c4764ffc2e681Phil     |   /   |
62153e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|--'    |->
62253e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
62353e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
62453e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
62553e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._high_send(msg)
62653e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
62753e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
62853e354dd0edd0d111fb7512f366c4764ffc2e681Phil
62953e354dd0edd0d111fb7512f366c4764ffc2e681Philclass DownDrain(Drain):
63053e354dd0edd0d111fb7512f366c4764ffc2e681Phil    """Repeat messages from high entry to low exit
63153e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
63253e354dd0edd0d111fb7512f366c4764ffc2e681Phil  >>-|--.    |->>
63353e354dd0edd0d111fb7512f366c4764ffc2e681Phil     |   \   |
63453e354dd0edd0d111fb7512f366c4764ffc2e681Phil   >-|    `--|->
63553e354dd0edd0d111fb7512f366c4764ffc2e681Phil     +-------+
63653e354dd0edd0d111fb7512f366c4764ffc2e681Phil"""
63753e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def push(self, msg):
63853e354dd0edd0d111fb7512f366c4764ffc2e681Phil        pass
63953e354dd0edd0d111fb7512f366c4764ffc2e681Phil    def high_push(self, msg):
64053e354dd0edd0d111fb7512f366c4764ffc2e681Phil        self._send(msg)
641