コード例 #1
0
    def run(self):
        """ query for current location """
        # ignore signals being used by main program
        signal.signal(signal.SIGINT,signal.SIG_IGN)
        signal.signal(signal.SIGTERM,signal.SIG_IGN)

        # get and transmit device details
        ret = self._devicedetails()
        if ret: self._icomms.put(('gpsd',isots(),GPS_GPSD,self._dd))

        # if fixed location, send one frontline trace otherwise poll over device
        if self._fixed:
            self._icomms.put(('gpsd',isots(),GPS_FLT,self._defFLT))
        else:
            while True:
                # block on our poll time (this will cause serial device to fill up
                if self._cI.poll(self._poll) and self._cI.recv() == POISON: break
                while self._gpsd.waiting():
                    # at each iteration make sure we did not get a poison pill
                    if self._cI.poll() and self._cI.recv() == POISON: break

                    # get the next input from the device
                    rpt = self._gpsd.next()
                    if rpt['class'] != 'TPV': continue

                    # try to read in all data
                    try:
                        if rpt['epx'] > self._qpx or rpt['epy'] > self._qpy:
                            continue
                        else:
                            flt = {'fix':rpt['mode'],
                               'coord':self._m.toMGRS(rpt['lat'],rpt['lon']),
                               'epy':rpt['epy'],
                               'epx':rpt['epx'],
                               'alt':rpt['alt'] if 'alt' in rpt else float("nan"),
                               'dir':rpt['track'] if 'track' in rpt else float("nan"),
                               'spd':rpt['spd'] if 'spd' in rpt else float("nan"),
                               'dop':{'xdop':'{0:.3}'.format(self._gpsd.xdop),
                                      'ydop':'{0:.3}'.format(self._gpsd.ydop),
                                      'pdop':'{0:.3}'.format(self._gpsd.pdop)}}
                            self._icomms.put(('gpsd',isots(),GPS_FLT,flt))
                            break
                    except (KeyError,AttributeError):
                        # a KeyError means not all values are present, an
                        # AttributeError means not all dop values are present
                        pass
                    except: # blanket exception quit device read loop
                        break

        # tell collator we're down, notify Iyri
        if ret: self._icomms.put(('gpsd',isots(),GPS_GPSD,None))
        self._cI.send((IYRI_DONE,'gpsd','',''))
        self._cI.close()
        if self._gpsd: self._gpsd.close()
コード例 #2
0
ファイル: thresh.py プロジェクト: molotof/wraith
    def run(self):
        """ execute frame process loop """
        # ignore signals used by main program & add signal for stop
        signal.signal(signal.SIGINT,
                      signal.SIG_IGN)  # CTRL-C and kill -INT stop
        signal.signal(signal.SIGTERM, signal.SIG_IGN)  # kill -TERM stop

        # local variables
        sid = None  # current session id
        rdos = {}  # (deprecated) radio map hwaddr->True
        stop = False  # stop flag

        # notify collator we're up
        self._icomms.put((self.cs, isots(), THRESH_THRESH, self.pid))

        while not stop:
            try:
                # do not check task queue until sid has been set
                ins = [self._cC, self._tasks._reader] if sid else [self._cC]
                (rs, _, _) = select.select(ins, [], [])
            except select.error as e:  # hide (4,'Interupted system call') errors
                if e[0] == 4: continue
                rs = []

            # get each message
            for r in rs:
                try:
                    # get & process data
                    if r == self._cC: (tkn, ts, d) = self._cC.recv()
                    else: (tkn, ts, d) = self._tasks.get(0.5)
                    if tkn == POISON: stop = True
                    elif tkn == COL_SID: sid = d[0]
                    elif tkn == COL_RDO: rdos[d[0]] = True
                    elif tkn == COL_FRAME: self._processframe(sid, ts, d)
                except Exception as e:
                    # blanket exception, catch all and notify collator
                    msg = "{0} {1}\n{2}".format(type(e).__name__, e, tb())
                    self._icomms.put(
                        (self.cs, isots(), THRESH_WARN, (msg, None)))

        # notify collector we're down
        if self._curs: self._curs.close()
        if self._conn: self._conn.close()
        self._icomms.put((self.cs, isots(), THRESH_THRESH, None))
コード例 #3
0
 def _newthresher(self, q, l, b, m):
     """
     creates a new thresher
     :param q: task queue for threshers
     :param l: STA lock
     :param b: buffer
     :param m: smap dict
     :returns tuple t = (thresher process id,send connection):
     """
     # NOTE: send the sid last so the thresher will not attempt to process
     # any frames until it has all radio(s) data
     r, s = mp.Pipe(False)
     t = Thresher(self._icomms, q, r, l, b, self._dbstr)
     t.start()
     for mac in m:
         ts = isots()
         s.send((COL_RDO, ts, [mac]))
     if self._sid: s.send((COL_SID, isots(), [self._sid]))
     return t.pid, s
コード例 #4
0
ファイル: thresh.py プロジェクト: molotof/wraith
    def _processframe(self, sid, ts, d):
        """
         inserts frame into db
         :param sid: session id
         :param ts: timestamp of frame
         :param d:  data
        """
        # (1) pull frame off the buffer & notify Collator
        src = idx = b = None
        try:
            src, idx, b = d  # hwaddr,index,nBytes
            f = self._buff[(idx * DIM_N):(idx * DIM_N) + b].tobytes()
            self._icomms.put((self.cs, isots(), THRESH_DQ, (None, idx)))
        except ValueError:
            self._icomms.put((self.cs, isots(), THRESH_ERR, 'Bad Data'))
            return
        except Exception as e:
            tr = tb()
            te = type(e).__name__
            if not src:
                msg = "Failed extracting data: {0}->{1}\n{2}".format(te, e, tr)
                self._icomms.put((self.cs, isots(), THRESH_ERR, (msg, None)))
            else:
                msg = "Failed extracting frame: {0}->{1}\n{2}".format(
                    te, e, tr)
                self._icomms.put((self.cs, isots(), THRESH_ERR, (msg, idx)))
            return

        # (2) parse the frame
        fid = None  # set an invalid frame id
        dR = {}  # make an empty rtap dict
        dM = mpdu.MPDU()  # & an empty mpdu dict
        try:
            lF = len(f)
            dR = rtap.parse(f)
            dM = mpdu.parse(f[dR['sz']:], rtap.flags_get(dR['flags'], 'fcs'))
            vs = (sid, ts, lF, dR['sz'], dM.offset, [(dR['sz'] + dM.offset),
                                                     (lF - dM.stripped)],
                  dR['flags'], int('a-mpdu' in dR['present']),
                  rtap.flags_get(dR['flags'], 'fcs'))
        except rtap.RadiotapException as e:
            # parsing failed @ radiotap, set empty vals & alert Collator
            msg = "Parsing failed at radiotap: {0} in buffer[{1}]".format(
                e, idx)
            self._icomms.put((self.cs, isots(), THRESH_WARN, (msg, None)))
            vs = (sid, ts, lF, 0, 0, [0, lF], 0, 0, 0)
            dR['err'] = e
        except mpdu.MPDUException as e:
            # mpdu failed - initialize empty mpdu vals & alert Collator
            msg = "Parsing failed at mpdu: {0} in buffer[{1}]".format(e, idx)
            self._icomms.put((self.cs, isots(), THRESH_WARN, (msg, None)))
            vs = (sid, ts, lF, dR['sz'], 0, [dR['sz'], lF], dR['flags'],
                  int('a-mpdu' in dR['present']),
                  rtap.flags_get(dR['flags'], 'fcs'))
        except Exception as e:  # blanket error
            msg = "Parsing Error. {0} {1}\n{2}".format(
                type(e).__name__, e, tb())
            self._icomms.put((self.cs, isots(), THRESH_WARN, (msg, idx)))
            return

        # (3) store the frame
        # NOTE: individual helper functions will commit database inserts/updates
        # but allow exceptions to fall through as db errors will be caught in this
        # try & rollbacks will be executed here
        try:
            # insert the frame and get frame's id
            self._next = 'frame'
            sql = """
                   insert into frame (sid,ts,bytes,bRTAP,bMPDU,data,flags,ampdu,fcs)
                   values (%s,%s,%s,%s,%s,%s,%s,%s,%s) returning frame_id;
                  """
            self._curs.execute(sql, vs)
            fid = self._curs.fetchone()[0]
            self._conn.commit()

            # write raw bytes
            self._next = 'frame_raw'
            sql = "insert into frame_raw (fid,raw) values (%s,%s);"
            self._curs.execute(sql, (fid, psql.Binary(f)))
            self._conn.commit()

            # insert radiotap and/or malformed if we had any issues
            self._next = 'radiotap'
            if not 'err' in dR: self._insertrtap(fid, src, dR)
            else:
                sql = """
                       insert into malformed (fid,location,reason)
                       values (%s,%s,%s);
                      """
                self._curs.execute(sql, (fid, 'radiotap', dR['err']))
                self._conn.commit()

            # insert mpdu and malfored (if it exists)
            for err in dM.error:
                sql = """
                       insert into malformed (fid,location,reason)
                       values (%s,%s,%s);
                      """
                self._curs.execute(sql, (fid, err[0], err[1]))
                self._conn.commit()

                # notify collator, but we want to continue attempting to store
                # as much as possible so set src and index to None
                msg = "Frame {0}. Bad MPDU {1}".format(fid, err)
                self._icomms.put((self.cs, isots(), THRESH_WARN, (msg, None)))

            # (a) store frame details in db

            if not dM.isempty:
                self._next = 'mpdu'
                self._insertmpdu(fid, dM)

                # (b) extract
                self._next = 'extract'

                # pull unique addresses of stas from mpdu into the addr dict
                # having the form: <hwaddr>->{'loc:[<i..n>], where i=1..4
                #                             'id':<sta_id>}
                # NOTE: insertsta modifies the addrs dict in place assigning ids
                # to each nonbroadcast address
                # sta addresses
                # Fct        To From    Addr1    Addr2  Addr3  Addr4
                # IBSS/Intra  0    0    RA=DA    TA=SA  BSSID    N/A
                # From AP     0    1    RA=DA TA=BSSID     SA    N/A
                # To AP       1    0 RA=BSSID    TA=SA     DA    N/A
                # Wireless DS 1    1 RA=BSSID TA=BSSID DA=WDS SA=WDS
                addrs = {}
                for i, a in enumerate(['addr1', 'addr2', 'addr3', 'addr4']):
                    if not a in dM: break
                    if dM[a] in addrs: addrs[dM[a]]['loc'].append(i + 1)
                    else: addrs[dM[a]] = {'loc': [i + 1], 'id': None}

                # insert the sta, sta_activity
                self._insertsta(fid, sid, ts, addrs)
                self._insertsta_activity(sid, ts, addrs)

                # further process mgmt frames?
                if dM.type == mpdu.FT_MGMT:
                    self._next = 'mgmt'
                    self._processmgmt(fid, sid, ts, addrs, dM)

            # notify collator, we're done
            self._icomms.put((self.cs, isots(), THRESH_DONE, (None, idx)))
        except psql.OperationalError as e:  # unrecoverable
            # NOTE: for now we do not exit on unrecoverable error but allow
            # collator to send the poison pill
            self._conn.rollback()
            args = (fid, self._next, e.pgcode, e.pgerror)
            msg = "<PSQL> Frame {0} failed at {1}. {2}->{3}.".format(*args)
            self._icomms.put((self.cs, isots(), THRESH_ERR, (msg, idx)))
        except psql.Error as e:
            # rollback to avoid db errors
            self._conn.rollback()
            args = (fid, self._next, e.pgcode, e.pgerror)
            msg = "<PSQL> Frame {0} failed at {1}. {2}->{3}.".format(*args)
            self._icomms.put((self.cs, isots(), THRESH_WARN, (msg, idx)))
        except Exception as e:
            self._conn.commit()  # commit anything that may be pending
            args = (fid, self._next, type(e).__name__, e, tb())
            msg = "<UNK> Frame {0} failed at {1}. {2}->{3}\n{4}.".format(*args)
            self._icomms.put((self.cs, isots(), THRESH_WARN, (msg, idx)))
コード例 #5
0
ファイル: rdoctl.py プロジェクト: molotof/wraith
    def run(self):
        """ run execution loop """
        # ignore signals being used by main program
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        signal.signal(signal.SIGTERM, signal.SIG_IGN)

        # start tuner
        stuner = TUNE_PAUSE if self._paused else TUNE_SCAN
        try:
            qT = mp.Queue()
            tuner = Tuner(qT, self._cI, self._rdo, self._scan, self._ds,
                          self._chi, stuner)
            tuner.start()
            self._icomms.put((self._rdo.vnic, isots(), RDO_RADIO, self.radio))
        except Exception as e:
            self._cI.send((IYRI_ERR, self._role, type(e).__name__, e))
            self._icomms.put((self._rdo.vnic, isots(), RDO_FAIL, e))
        else:
            msg = "tuner-{0} up".format(tuner.pid)
            self._cI.send((IYRI_INFO, self._role, 'Tuner', msg))

        # execute sniffing loop
        # RadioController will check for any notifications from the Tuner, a tuple
        # t = (event,timestamp,message) where message is another tuple of the form
        # m = (cmdid,description) such that cmdid != -1 if this command comes from
        # an external source. If there are no notifications from Tuner, check if
        # any frames are queued on the socket and read them in to the buffer.
        # If we're paused, read into a null buffer
        ix = 0  # index of current frame
        m = DIM_M  # save some space with
        n = DIM_N  # these
        err = False  # error state, do nothing until we get POISON pill
        while True:
            try:
                # pull any notification from Tuner & process
                ev, ts, msg = qT.get_nowait()
                if ev == POISON: break
                elif ev == CMD_ERR:
                    self._cI.send((CMD_ERR, self._role, msg[0], msg[1]))
                elif ev == RDO_FAIL:
                    self._cI.send((IYRI_ERR, self._role, 'Tuner', msg[1]))
                    self._icomms.put((self._rdo.vnic, ts, RDO_FAIL, msg[1]))
                elif ev == RDO_STATE:
                    self._cI.send((CMD_ACK, self._role, msg[0], msg[1]))
                elif ev == RDO_HOLD:
                    stuner = TUNE_HOLD
                    self._icomms.put((self._rdo.vnic, ts, RDO_HOLD, msg[1]))
                    if msg[0] > -1:
                        self._cI.send((CMD_ACK, self._role, msg[0], msg[1]))
                elif ev == RDO_SCAN:
                    stuner = TUNE_SCAN
                    self._icomms.put((self._rdo.vnic, ts, RDO_SCAN, msg[1]))
                    if msg[0] > -1:
                        self._cI.send((CMD_ACK, self._role, msg[0], msg[1]))
                elif ev == RDO_LISTEN:
                    stuner = TUNE_LISTEN
                    self._icomms.put((self._rdo.vnic, ts, RDO_LISTEN, msg[1]))
                    if msg[0] > -1:
                        self._cI.send((CMD_ACK, self._role, msg[0], msg[1]))
                elif ev == RDO_PAUSE:
                    stuner = TUNE_PAUSE
                    self._icomms.put((self._rdo.vnic, ts, RDO_PAUSE, ' '))
                    if msg[0] > -1:
                        self._cI.send((CMD_ACK, self._role, msg[0], msg[1]))
            except Empty:  # no notices from Tuner
                if err:
                    continue  # error state, wait till iyri sends poison pill
                try:
                    # if paused, pull any frame off and ignore otherwise pass on
                    if stuner == TUNE_PAUSE: _ = self._s.recv(n)
                    else:
                        l = self._s.recv_into(
                            self._buffer[(ix * n):(ix * n) + n], n)
                        self._icomms.put(
                            (self._rdo.vnic, isots(), RDO_FRAME, (ix, l)))
                        ix = (ix + 1) % m
                except socket.timeout:
                    pass
                except socket.error as e:
                    err = True
                    self._cI.send((IYRI_ERR, self._role, 'Socket', e))
                    self._icomms.put((self._rdo.vnic, isots(), RDO_FAIL, e))
                except Exception as e:
                    err = True
                    self._cI.send((IYRI_ERR, self._role, type(e).__name__, e))
                    self._icomms.put((self._rdo.vnic, isots(), RDO_FAIL, e))

        # wait on Tuner to finish
        while mp.active_children():
            sleep(0.5)

        # notify Iyri and Collator we are down & shut down
        self._cI.send((IYRI_DONE, self._role, 'done', 'done'))
        self._icomms.put((self._rdo.vnic, isots(), RDO_RADIO, None))
        if not self._shutdown():
            try:
                self._cI.send(
                    (IYRI_WARN, self._role, 'Shutdown', "Incomplete reset"))
            except (EOFError, IOError):
                pass
コード例 #6
0
    def run(self):
        """ switch channels based on associted dwell times """
        # ignore signals being used by main program
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        signal.signal(signal.SIGTERM, signal.SIG_IGN)

        # starting paused or scanning?
        if self._state == TUNE_PAUSE:
            self._qR.put((RDO_PAUSE, isots(), [-1, ' ']))
        else:
            self._qR.put((RDO_SCAN, isots(), [-1, self._chs]))

        # execution loop - wait on the internal connection for each channels
        # dwell time. IOT avoid a state where we would continue to scan the same
        # channel, i.e. 'state' commands, use a remaining time counter
        remaining = 0  # remaining dwell time counter
        dwell = self._ds[0]  # save original dwell time
        while True:
            # set the poll timeout to remaining if we need to finish this scan
            # or to None if we are in a static state
            if self._state in [TUNE_PAUSE, TUNE_HOLD, TUNE_LISTEN]: to = None
            else: to = self._ds[self._i] if not remaining else remaining

            ts1 = time()  # get timestamp
            if self._cI.poll(
                    to):  # we hold on the iyri connection during poll time
                tkn = self._cI.recv()
                ts = time()

                # tokens will have 2 flavor's POISON and 'cmd:cmdid:params'
                # where params is empty (for POISON) or a '-' separated list
                if tkn == POISON:
                    # for a POISON, quit and notify the RadioController
                    self._qR.put((POISON, ts2iso(ts), [-1, ' ']))
                    break

                # calculate time remaining for this channel
                if not remaining: remaining = self._ds[self._i] - (ts - ts1)
                else: remaining -= (ts - ts1)

                # parse the requested command
                cmd, cid, ps = tkn.split(':')  # force into 3 components
                cid = int(cid)  # & convert id to int
                if cmd == 'state':
                    self._qR.put((RDO_STATE, ts2iso(ts), [cid, self.meta]))
                elif cmd == 'scan':
                    if self._state != TUNE_SCAN:
                        self._state = TUNE_SCAN
                        self._qR.put((RDO_SCAN, ts2iso(ts), [cid, self._chs]))
                    else:
                        self._qR.put(
                            (CMD_ERR, ts2iso(ts), [cid, "redundant cmd"]))
                elif cmd == 'txpwr':
                    err = "txpwr not currently supported"
                    self._qR.put((CMD_ERR, ts2iso(ts), [cid, err]))
                elif cmd == 'spoof':
                    err = "spoof not currently supported"
                    self._qR.put((CMD_ERR, ts2iso(ts), [cid, err]))
                elif cmd == 'hold':
                    if self._state != TUNE_HOLD:
                        self._state = TUNE_HOLD
                        self._qR.put(
                            (RDO_HOLD, ts2iso(ts), [cid, self.channel]))
                    else:
                        self._qR.put(
                            (CMD_ERR, ts2iso(ts), [cid, "redundant cmd"]))
                elif cmd == 'pause':
                    if self._state != TUNE_PAUSE:
                        self._state = TUNE_PAUSE
                        self._qR.put(
                            (RDO_PAUSE, ts2iso(ts), [cid, self.channel]))
                    else:
                        self._qR.put(
                            (CMD_ERR, ts2iso(ts), [cid, "redundant cmd"]))
                elif cmd == 'listen':
                    if self._state != TUNE_LISTEN:
                        try:
                            ch, chw = ps.split('-')
                            if chw == 'None': chw = None
                            self._rdo.setch(ch, chw)
                            self._state = TUNE_LISTEN
                            details = "{0}:{1}".format(ch, chw)
                            self._qR.put(
                                (RDO_LISTEN, ts2iso(ts), [cid, details]))
                        except ValueError:
                            err = "invalid param format"
                            self._qR.put((CMD_ERR, ts2iso(ts), [cid, err]))
                        except radio.RadioException as e:
                            self._qR.put((CMD_ERR, ts2iso(ts), [cid, str(e)]))
                    else:
                        self._qR.put(
                            (CMD_ERR, ts2iso(ts), [cid, "redundant cmd"]))
                else:
                    err = "invalid command {0}".format(cmd)
                    self._qR.put((CMD_ERR, ts2iso(ts), [cid, err]))
            else:
                try:
                    # no token, go to next channel, reset remaining
                    self._i = (self._i + 1) % len(self._chs)
                    self._rdo.setch(self._chs[self._i][0],
                                    self._chs[self._i][1])
                    remaining = 0  # reset remaining timeout
                except radio.RadioException as e:
                    self._qR.put((RDO_FAIL, isots(), [-1, e]))
                except Exception as e:  # blanket exception
                    self._qR.put((RDO_FAIL, isots(), [-1, e]))
コード例 #7
0
    def run(self):
        """ run execution loop """
        # ignore signals being used by main program
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        signal.signal(signal.SIGTERM, signal.SIG_IGN)

        # function local variables
        mint = mp.cpu_count()  # min/initial # of threshers
        maxt = self._ps['max']  # maximum # of threshers
        lSta = mp.Lock()  # sta lock
        qT = mp.Queue()  # thresher task queue
        rmap = {}  # maps callsigns (vnic) to hwaddr
        fmap = {}  # maps src (hwaddr) to frames
        smap = {}  # maps src (hwaddr) to sensor params
        threshers = {}  # pid->connection
        tst = 0  # timestamp of last thresher creation
        stop = False  # poison pill has been received
        poisoned = False  # poison pill has been sent (to threshers)
        df = rf = 0  # number dropped frames & read frames

        # create our frame buffer
        m = DIM_M * 5  # set max # of frames
        n = DIM_N  # and keep n as is
        _ix = 0  # current 'pointer'
        cb = memoryview(mp.Array('B', m * n, lock=False))

        # send sensor up notification, platform details & create threshers
        try:
            self._submitsensor(isots(), True)
            for _ in xrange(mint):
                try:
                    (pid, send) = self._newthresher(qT, lSta, cb, smap)
                    threshers[pid] = send
                except RuntimeError as e:
                    self._cI.send((IYRI_WARN, 'collator', 'Thresher', e))
            tst = time()
            self._submitplatform()
        except psql.Error as e:
            if e.pgcode == '23P01': err = "Last session closed incorrectly"
            else: err = "{0}->{1}".format(e.pgcode, e.pgerror)
            self._conn.rollback()
            self._cI.send((IYRI_ERR, 'collator', 'Thresher', err))

        # notify iyri of startup parameters
        msg = "{0} threshers (min={1},max={2}) & a {3} x {4} internal buffer"
        msg = msg.format(format(len(threshers)), mint, maxt, m, n)
        self._cI.send((IYRI_INFO, 'collator', 'Startup', msg))

        # execution loop - NOTE: if submitsensor failed, there will be no
        # threshers and we will immediately fall through the exection loop
        ins = [self._cI, self._icomms._reader]
        while mp.active_children():
            # if we have stop, send out the poison pills once all frames have been
            # processed (or at least pulled of the buffer)
            if stop:
                if not [i for i in fmap if fmap[i]] and not poisoned:
                    msg = "{0} outstanding frames".format(len(fmap))
                    for pid in threshers:
                        try:
                            threshers[pid].send((POISON, None, None))
                        except Exception as e:
                            msg = "Error stopping Thresher-{0}: {1}".format(
                                pid, e)
                            self._cI.send(
                                (IYRI_WARN, 'collator', 'Thresher', e))
                    poisoned = True

            # continue with processing
            try:
                # block longer if we havent gotten poison pill
                (rs, _, _) = select.select(ins, [], [], 0.5 if stop else 5)

                # convert tkns from iyri to icomms format
                # NOTE: all Thresh events (exluding THRESH_THRESH) will return
                # data as a tuple t = (string message,buffer index) where any
                # individual item in the tuple could be None
                for r in rs:
                    if r == self._cI:
                        (cs, ts, ev, d) = ('', '', self._cI.recv(), '')
                    else:
                        (cs, ts, ev, d) = self._icomms.get_nowait()

                    if ev == POISON: stop = True
                    elif ev == THRESH_THRESH:  # thresher process up/down message
                        if d is not None:  # thresher up
                            msg = "{0} up".format(cs)
                            self._cI.send(
                                (IYRI_INFO, 'collator', 'Thresher', msg))
                        else:  # thresher down
                            pid = int(cs.split('-')[1])
                            threshers[pid].close()
                            del threshers[pid]
                            msg = "{0} down".format(cs)
                            self._cI.send(
                                (IYRI_INFO, 'collator', 'Thresher', msg))
                    elif ev == THRESH_DQ:  # frame has been pulled off buffer
                        (_, idx) = d
                        fmap[idx] = 0
                    elif ev == THRESH_WARN or ev == THRESH_ERR or ev == THRESH_DONE:
                        # frame processing finished
                        msg, idx = d
                        try:
                            del fmap[idx]
                        except:
                            pass

                        # handle individual events below
                        if ev == THRESH_WARN:
                            self._cI.send(
                                (IYRI_WARN, 'collator', 'Thresher', msg))
                        elif ev == THRESH_ERR:
                            pid = int(cs.split('-')[1])
                            msg = "{0}->{1}".format(cs, msg[0])
                            self._cI.send(
                                (IYRI_WARN, 'collator', 'Thresher', msg))
                            threshers[pid].send((POISON, None, None))

                            # make a new thresher (to replace this one)
                            try:
                                (pid,
                                 send) = self._newthresher(qT, lSta, cb, smap)
                                threshers[pid] = send
                            except RuntimeError as e:
                                self._cI.send(
                                    (IYRI_WARN, 'Collator', Thresher, e))

                        # determine workload percentage
                        if stop: continue
                        if len(fmap) / (m *
                                        1.0) <= 0.1 and len(threshers) > mint:
                            msg = "Workload falling below 10%, removing a threseher"
                            self._cI.send((IYRI_INFO, 'collator', 'load', msg))
                            pid = threshers.keys()[0]
                            threshers[pid].send((POISON, None, None))
                    elif ev == GPS_GPSD:  # gpsd up/down
                        self._submitgpsd(ts, d)
                        if d:
                            self._cI.send(
                                (IYRI_INFO, 'collator', 'GPSD', 'initiated'))
                    elif ev == GPS_FLT:
                        self._submitflt(ts, d)
                    elif ev == RDO_RADIO:  # up/down message from radio(s).
                        if d is not None:  # radio up
                            (mac, role) = d['mac'], d['role']

                            # shouldn't happen but make sure:
                            assert (cs not in rmap)  # reinitialization
                            assert (role in ['abad', 'shama'])  # role is valid

                            # notify iyri radio is up & add to maps
                            msg = "{0}:{1} initiated".format(role, cs)
                            self._cI.send(
                                (IYRI_INFO, 'collator', 'Radio', msg))
                            rmap[cs] = mac
                            smap[mac] = {'cb': None}
                            if role == 'abad': smap[mac]['cb'] = self._ab
                            else: smap[mac]['cb'] = self._sb

                            # update threshers
                            for pid in threshers:
                                threshers[pid].send((COL_RDO, ts, [mac]))

                            # submit radio & antenna(s)
                            self._submitradio(ts, mac, d)
                            for i in xrange(d['nA']):
                                self._submitantenna(
                                    ts, {
                                        'mac': mac,
                                        'idx': i,
                                        'type': d['type'][i],
                                        'gain': d['gain'][i],
                                        'loss': d['loss'][i],
                                        'x': d['x'][i],
                                        'y': d['y'][i],
                                        'z': d['z'][i]
                                    })
                        else:  # radio down
                            msg = "{0} shutdown".format(cs)
                            self._cI.send(
                                (IYRI_INFO, 'collator', 'Radio', msg))
                            self._submitradio(ts, rmap[cs], None)

                            # delete our maps (only delete tasks if its empty)
                            del smap[rmap[cs]]
                            del rmap[cs]
                    elif ev == RDO_FAIL:
                        msg = "Deleting radio {0}: {1}".format(cs, d)
                        self._cI.send((IYRI_ERR, 'collator', 'Radio', msg))
                        self._submitradioevent(ts, [rmap[cs], 'fail', d])

                        # delete our maps
                        del smap[rmap[cs]]
                        del rmap[cs]
                    elif RDO_SCAN <= ev <= RDO_STATE:
                        if ev == RDO_SCAN:  # need to format the channel list
                            d = ','.join(
                                ["{0}:{1}".format(c, w) for (c, w) in d])
                        self._cI.send((IYRI_INFO, cs, RDO_DESC[ev], d))
                        self._submitradioevent(ts, [rmap[cs], RDO_DESC[ev], d])
                    elif ev == RDO_FRAME:
                        # get the index,length of the frame & src hwaddr
                        (i, l) = d
                        hw = rmap[cs]

                        # about to overwrite a frame?
                        if _ix in fmap and fmap[_ix] == 1:
                            df += 1
                            print 'dropping'
                            continue

                        # move to our buffer, put on threshers queue & update index
                        cb[(_ix * n):(_ix * n) +
                           l] = smap[hw]['cb'][(i * n):(i * n) + l]
                        fmap[_ix] = 1
                        qT.put((COL_FRAME, ts, (hw, _ix, l)))
                        _ix = (_ix + 1) % m
                        rf += 1

                        # if the buffer reaches 25% capacity, add a thresher
                        # NOTE: consider all outstanding frames even those that
                        # have been pulled of the buffer but are still being
                        # processed)
                        if len(fmap) / (m *
                                        1.0) > 0.25 and len(threshers) < maxt:
                            # create at most mint new threshers w/out adding
                            # more than allowed. Give time for previously
                            # created threshers to start working
                            tsnow = time()
                            if tsnow - tst < 3: continue
                            x = min(mint, maxt - len(threshers))
                            msg = "Buffer at 25%, adding {0} threshers".format(
                                x)
                            self._cI.send((IYRI_INFO, 'collator', 'load', msg))
                            for _ in xrange(x):
                                try:
                                    (pid, send) = self._newthresher(
                                        qT, lSta, cb, smap)
                                    threshers[pid] = send
                                except RuntimeError as e:
                                    self._cI.send(
                                        (IYRI_WARN, 'Collator', Thresher, e))
                            tst = time()
                    else:  # unidentified event type, notify iyri
                        msg = "unknown event. {0}->{1}".format(ev, cs)
                        self._cI.send((IYRI_WARN, 'collator', 'icomms', msg))
            except Empty:
                continue
            except (EOFError, IOError):
                pass
            except psql.OperationalError as e:  # unrecoverable
                rmsg = "{0}: {1}".format(e.pgcode, e.pgerror)
                self._cI.send((IYRI_ERR, 'collator', 'DB', rmsg))
            except psql.Error as e:  # possible incorrect semantics/syntax
                self._conn.rollback()
                rmsg = "{0}: {1}".format(e.pgcode, e.pgerror)
                self._cI.send((IYRI_WARN, 'collator', 'SQL', rmsg))
            except IndexError as e:
                rmsg = "Bad index {0} in event {1}".format(e, ev)
                self._cI.send((IYRI_ERR, 'collator', "Index", rmsg))
            except KeyError as e:
                rmsg = "Bad key {0} in event {1}".format(e, ev)
                self._cI.send((IYRI_ERR, 'collator', 'Key', rmsg))
            except select.error as e:  # hide (4,'Interupted system call') errors
                if e[0] != 4:
                    self._cI.send((IYRI_WARN, 'collator', 'select', e))
            except Exception as e:  # handle catchall error
                self._cI.send((IYRI_WARN, 'collator', type(e).__name__, e))

        # clean up & shut down
        try:
            try:
                # notify Nidus of sensor - check if radio(s), gpsd closed out
                for cs in rmap:
                    self._submitradio(isots(), rmap[cs], None)
                if self._gid: self._submitgpsd(isots(), None)
                if self._sid: self._submitsensor(isots(), False)
            except psql.Error:
                self._conn.rollback()
                self._cI.send(
                    (IYRI_WARN, 'collator', 'shutdown', "DB is corrupted"))

            if self._curs: self._curs.close()
            if self._conn: self._conn.close()
            if rf or df:
                msg = "{0} read frames, {1} dropped frames".format(rf, df)
                self._cI.send((IYRI_DONE, 'collator', 'Complete', msg))
            self._cI.close()
        except (EOFError, IOError):
            pass
        except Exception as e:
            if self._cI and not self._cI.closed:
                msg = "Failed to clean up: {0}".format(e)
                self._cI.send((IYRI_WARN, 'collator', 'shutdown', msg))