From 2c60aa639594ccbf435b17801500fdca183b05f1 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 24 Apr 2016 21:08:07 -0700 Subject: [PATCH] Separate UID/SID generators into various protocol modules (#199) --- protocols/hybrid.py | 4 +- protocols/inspircd.py | 6 +-- protocols/nefarious.py | 10 ++++- protocols/ts6.py | 4 +- protocols/ts6_common.py | 91 +++++++++++++++++++++++++++++++++++++- protocols/unreal.py | 4 +- utils.py | 96 ----------------------------------------- 7 files changed, 108 insertions(+), 107 deletions(-) diff --git a/protocols/hybrid.py b/protocols/hybrid.py index 3a07f9c..079d225 100644 --- a/protocols/hybrid.py +++ b/protocols/hybrid.py @@ -9,7 +9,7 @@ import utils from log import log from classes import * -from ts6 import TS6Protocol +from ts6 import * class HybridProtocol(TS6Protocol): def __init__(self, irc): @@ -100,7 +100,7 @@ class HybridProtocol(TS6Protocol): raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server) # Create an UIDGenerator instance for every SID, so that each gets # distinct values. - uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid() + uid = self.uidgen.setdefault(server, TS6UIDGenerator(server)).next_uid() # EUID: # parameters: nickname, hopcount, nickTS, umodes, username, # visible hostname, IP address, UID, real hostname, account name, gecos diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 36fd9cd..2300f08 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -15,7 +15,7 @@ import utils from log import log from classes import * -from ts6_common import TS6BaseProtocol +from ts6_common import * class InspIRCdProtocol(TS6BaseProtocol): def __init__(self, irc): @@ -48,7 +48,7 @@ class InspIRCdProtocol(TS6BaseProtocol): raise ValueError('Server %r is not a PyLink server!' % server) # Create an UIDGenerator instance for every SID, so that each gets # distinct values. - uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid() + uid = self.uidgen.setdefault(server, TS6UIDGenerator(server)).next_uid() ts = ts or int(time.time()) realname = realname or self.irc.botdata['realname'] realhost = realhost or host @@ -390,7 +390,7 @@ class InspIRCdProtocol(TS6BaseProtocol): # name it anything you like. The former is config default, # but I personally prefer the latter. name = 'owner' - + if name == 'c_registered': # Be consistent with other protocols name = 'registered' diff --git a/protocols/nefarious.py b/protocols/nefarious.py index cfedfff..11e4fec 100644 --- a/protocols/nefarious.py +++ b/protocols/nefarious.py @@ -16,6 +16,14 @@ import utils from log import log from classes import * +class P10UIDGenerator(utils.IncrementalUIDGenerator): + """Implements an incremental P10 UID Generator.""" + + def __init__(self, sid): + self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[]' + self.length = 3 + super().__init__(sid) + def p10b64encode(num, length=2): """ Encodes a given numeric using P10 Base64 numeric nicks, as documented at @@ -242,7 +250,7 @@ class P10Protocol(Protocol): # Create an UIDGenerator instance for every SID, so that each gets # distinct values. - uid = self.uidgen.setdefault(server, utils.P10UIDGenerator(server)).next_uid() + uid = self.uidgen.setdefault(server, P10UIDGenerator(server)).next_uid() # Fill in all the values we need ts = ts or int(time.time()) diff --git a/protocols/ts6.py b/protocols/ts6.py index e602d15..a6529bc 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -14,7 +14,7 @@ import utils from log import log from classes import * -from ts6_common import TS6BaseProtocol +from ts6_common import * class TS6Protocol(TS6BaseProtocol): def __init__(self, irc): @@ -40,7 +40,7 @@ class TS6Protocol(TS6BaseProtocol): raise ValueError('Server %r is not a PyLink server!' % server) # Create an UIDGenerator instance for every SID, so that each gets # distinct values. - uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid() + uid = self.uidgen.setdefault(server, TS6UIDGenerator(server)).next_uid() # EUID: # parameters: nickname, hopcount, nickTS, umodes, username, # visible hostname, IP address, UID, real hostname, account name, gecos diff --git a/protocols/ts6_common.py b/protocols/ts6_common.py index 13750b5..ba2a2d3 100644 --- a/protocols/ts6_common.py +++ b/protocols/ts6_common.py @@ -4,6 +4,7 @@ ts6_common.py: Common base protocol class with functions shared by the UnrealIRC import sys import os +import string # Import hacks to access utils and classes... curdir = os.path.dirname(__file__) @@ -13,6 +14,94 @@ import utils from log import log from classes import * +class TS6SIDGenerator(): + """ + TS6 SID Generator. is a 3 character string with any combination of + uppercase letters, digits, and #'s. it must contain at least one #, + which are used by the generator as a wildcard. On every next_sid() call, + the first available wildcard character (from the right) will be + incremented to generate the next SID. + + When there are no more available SIDs left (SIDs are not reused, only + incremented), RuntimeError is raised. + + Example queries: + "1#A" would give: 10A, 11A, 12A ... 19A, 1AA, 1BA ... 1ZA (36 total results) + "#BQ" would give: 0BQ, 1BQ, 2BQ ... 9BQ (10 total results) + "6##" would give: 600, 601, 602, ... 60Y, 60Z, 610, 611, ... 6ZZ (1296 total results) + """ + + def __init__(self, irc): + self.irc = irc + try: + self.query = query = list(irc.serverdata["sidrange"]) + except KeyError: + raise RuntimeError('(%s) "sidrange" is missing from your server configuration block!' % irc.name) + + self.iters = self.query.copy() + self.output = self.query.copy() + self.allowedchars = {} + qlen = len(query) + + assert qlen == 3, 'Incorrect length for a SID (must be 3, got %s)' % qlen + assert '#' in query, "Must be at least one wildcard (#) in query" + + for idx, char in enumerate(query): + # Iterate over each character in the query string we got, along + # with its index in the string. + assert char in (string.digits+string.ascii_uppercase+"#"), \ + "Invalid character %r found." % char + if char == '#': + if idx == 0: # The first char be only digits + self.allowedchars[idx] = string.digits + else: + self.allowedchars[idx] = string.digits+string.ascii_uppercase + self.iters[idx] = iter(self.allowedchars[idx]) + self.output[idx] = self.allowedchars[idx][0] + next(self.iters[idx]) + + + def increment(self, pos=2): + """ + Increments the SID generator to the next available SID. + """ + if pos < 0: + # Oh no, we've wrapped back to the start! + raise RuntimeError('No more available SIDs!') + it = self.iters[pos] + try: + self.output[pos] = next(it) + except TypeError: # This position is not an iterator, but a string. + self.increment(pos-1) + except StopIteration: + self.output[pos] = self.allowedchars[pos][0] + self.iters[pos] = iter(self.allowedchars[pos]) + next(self.iters[pos]) + self.increment(pos-1) + + def next_sid(self): + """ + Returns the next unused TS6 SID for the server. + """ + while ''.join(self.output) in self.irc.servers: + # Increment until the SID we have doesn't already exist. + self.increment() + sid = ''.join(self.output) + return sid + +class TS6UIDGenerator(utils.IncrementalUIDGenerator): + """Implements an incremental TS6 UID Generator.""" + + def __init__(self, sid): + # Define the options for IncrementalUIDGenerator, and then + # initialize its functions. + # TS6 UIDs are 6 characters in length (9 including the SID). + # They go from ABCDEFGHIJKLMNOPQRSTUVWXYZ -> 0123456789 -> wrap around: + # e.g. AAAAAA, AAAAAB ..., AAAAA8, AAAAA9, AAAABA, etc. + self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456879' + self.length = 6 + super().__init__(sid) + class TS6BaseProtocol(Protocol): def __init__(self, irc): @@ -22,7 +111,7 @@ class TS6BaseProtocol(Protocol): self.uidgen = {} # SID generator for TS6. - self.sidgen = utils.TS6SIDGenerator(irc) + self.sidgen = TS6SIDGenerator(irc) def _send(self, source, msg): """Sends a TS6-style raw command from a source numeric to the self.irc connection given.""" diff --git a/protocols/unreal.py b/protocols/unreal.py index 337a833..bbd363c 100644 --- a/protocols/unreal.py +++ b/protocols/unreal.py @@ -16,7 +16,7 @@ sys.path += [curdir, os.path.dirname(curdir)] import utils from log import log from classes import * -from ts6_common import TS6BaseProtocol +from ts6_common import * class UnrealProtocol(TS6BaseProtocol): def __init__(self, irc): @@ -72,7 +72,7 @@ class UnrealProtocol(TS6BaseProtocol): raise ValueError('Server %r is not a PyLink server!' % server) # Unreal 4.0 uses TS6-style UIDs. They don't start from AAAAAA like other IRCd's # do, but that doesn't matter to us... - uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid() + uid = self.uidgen.setdefault(server, TS6UIDGenerator(server)).next_uid() ts = ts or int(time.time()) realname = realname or self.irc.botdata['realname'] realhost = realhost or host diff --git a/utils.py b/utils.py index 8bd344e..5b0c517 100644 --- a/utils.py +++ b/utils.py @@ -43,9 +43,6 @@ class IncrementalUIDGenerator(): """ def __init__(self, sid): - # TS6 UIDs are 6 characters in length (9 including the SID). - # They wrap from ABCDEFGHIJKLMNOPQRSTUVWXYZ -> 0123456789 -> wrap around: - # (e.g. AAAAAA, AAAAAB ..., AAAAA8, AAAAA9, AAAABA) if not (hasattr(self, 'allowedchars') and hasattr(self, 'length')): raise RuntimeError("Allowed characters list not defined. Subclass " "%s by defining self.allowedchars and self.length " @@ -80,99 +77,6 @@ class IncrementalUIDGenerator(): self.increment() return uid -class TS6UIDGenerator(IncrementalUIDGenerator): - """Implements an incremental TS6 UID Generator.""" - - def __init__(self, sid): - # Define the options for IncrementalUIDGenerator, and then - # initialize its functions. - self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456879' - self.length = 6 - super().__init__(sid) - -class P10UIDGenerator(IncrementalUIDGenerator): - """Implements an incremental P10 UID Generator.""" - - def __init__(self, sid): - self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[]' - self.length = 3 - super().__init__(sid) - -class TS6SIDGenerator(): - """ - TS6 SID Generator. is a 3 character string with any combination of - uppercase letters, digits, and #'s. it must contain at least one #, - which are used by the generator as a wildcard. On every next_sid() call, - the first available wildcard character (from the right) will be - incremented to generate the next SID. - - When there are no more available SIDs left (SIDs are not reused, only - incremented), RuntimeError is raised. - - Example queries: - "1#A" would give: 10A, 11A, 12A ... 19A, 1AA, 1BA ... 1ZA (36 total results) - "#BQ" would give: 0BQ, 1BQ, 2BQ ... 9BQ (10 total results) - "6##" would give: 600, 601, 602, ... 60Y, 60Z, 610, 611, ... 6ZZ (1296 total results) - """ - - def __init__(self, irc): - self.irc = irc - try: - self.query = query = list(irc.serverdata["sidrange"]) - except KeyError: - raise RuntimeError('(%s) "sidrange" is missing from your server configuration block!' % irc.name) - - self.iters = self.query.copy() - self.output = self.query.copy() - self.allowedchars = {} - qlen = len(query) - - assert qlen == 3, 'Incorrect length for a SID (must be 3, got %s)' % qlen - assert '#' in query, "Must be at least one wildcard (#) in query" - - for idx, char in enumerate(query): - # Iterate over each character in the query string we got, along - # with its index in the string. - assert char in (string.digits+string.ascii_uppercase+"#"), \ - "Invalid character %r found." % char - if char == '#': - if idx == 0: # The first char be only digits - self.allowedchars[idx] = string.digits - else: - self.allowedchars[idx] = string.digits+string.ascii_uppercase - self.iters[idx] = iter(self.allowedchars[idx]) - self.output[idx] = self.allowedchars[idx][0] - next(self.iters[idx]) - - - def increment(self, pos=2): - """ - Increments the SID generator to the next available SID. - """ - if pos < 0: - # Oh no, we've wrapped back to the start! - raise RuntimeError('No more available SIDs!') - it = self.iters[pos] - try: - self.output[pos] = next(it) - except TypeError: # This position is not an iterator, but a string. - self.increment(pos-1) - except StopIteration: - self.output[pos] = self.allowedchars[pos][0] - self.iters[pos] = iter(self.allowedchars[pos]) - next(self.iters[pos]) - self.increment(pos-1) - - def next_sid(self): - """ - Returns the next unused TS6 SID for the server. - """ - while ''.join(self.output) in self.irc.servers: - # Increment until the SID we have doesn't already exist. - self.increment() - sid = ''.join(self.output) - return sid - def add_cmd(func, name=None): """Binds an IRC command function to the given command name.""" if name is None: