def pns_to_xml_utf8(model, xml_types={}, xml_type=xml_dom.Element): # try to decode the PNS/XML element try: attr, first, children, follow = netstring.validate(model[2], 4) except: if model[0] != model[3]: attr = {'pns': model[0]} else: attr = None e = xml_types.get(model[1], xml_type)(model[1] or 'http://allegra/ pns-xml-error', attr) e.xml_first = model[2] return e, None # decode the attributes and set the pns attribute if attr: attr = dict( (tuple(netstring.decode(item)) for item in netstring.decode(attr))) if model[0] != model[3]: attr['pns'] = model[0] elif model[0] != model[3]: attr = {'pns': model[0]} else: attr = None e = xml_types.get(model[1], xml_type)(model[1], attr) if first: e.xml_first = first else: e.xml_first = '' if follow: e.xml_follow = follow return e, children
def pns_to_xml_unicode (self, resolved, model): self.xml_parsed, children = pns_to_xml_unicode ( model, self.pns_dom.xml_types, self.pns_dom.xml_type ) if children: self.xml_parsed.xml_children = children = list ( netstring.decode (children) ) for child in children: subject, name = tuple ( netstring.decode (child) ) if subject: context = model[0] else: context = subject = model[0] joined = PNS_XML_continuation ( self.pns_dom, child ) joined.pns_context = model[3] self.pns_dom.pns_statement ( (subject, name, ''), context, joined.pns_to_xml_unicode ) joined.finalization = self
def statement_unicode (model, prefixes, encoding='ASCII'): e, children = pns_to_xml_unicode (model) if children: e.xml_children = [] for child in netstring.decode (children): subject, name = netstring.decode (child) if subject: e.xml_children.append ('<%s pns="%s"/>' % ( ''.join (xml_unicode.xml_prefix_FQN ( unicode (name, 'utf-8'), prefixes, encoding )), xml_unicode.xml_attr ( unicode (subject, 'utf-8'), encoding ) )) else: e.xml_children.append ( '<%s%s' ' />' % xml_unicode.xml_prefix_FQN ( unicode (name, 'utf-8'), prefixes, encoding ) ) return xml_unicode.xml_prefixed ( e, prefixes, ' context="%s"' % xml_unicode.xml_attr ( unicode (model[3], 'utf-8'), encoding ), encoding )
def pns_to_xml_utf8 (self, resolved, model): if model[4][0] in ('.', '?'): return False self.xml_parsed, children = pns_to_xml_utf8 ( model, self.pns_dom.xml_types, self.pns_dom.xml_type ) # decode the children's name and subject, if children: self.xml_parsed.xml_children = children = list ( netstring.decode (children) ) for child in children: subject, name = tuple ( netstring.decode (child) ) if subject: context = model[0] else: context = subject = model[0] joined = PNS_XML_continuation ( self.pns_dom, child ) self.pns_dom.pns_statement ( (subject, name, ''), context, joined.pns_to_xml_unicode ) joined.finalization = self return False
def statements_unicode(model, prefixes, contexts, encoding='ASCII'): for co in netstring.decode(model[2]): c, o = netstring.decode(co) if c not in contexts: continue for more in statement_unicode((model[0], model[1], o, c, '_'), prefixes, encoding): yield more
def statements_unicode (model, prefixes, contexts, encoding='ASCII'): for co in netstring.decode (model[2]): c, o = netstring.decode (co) if c not in contexts: continue for more in statement_unicode ( (model[0], model[1], o, c, '_'), prefixes, encoding ): yield more
def pns_to_xml_unicode_a(self, resolved, model): if model[4] != ('_') or model[2] == '': return False self.pns_contexts = {} question = resolved[:2] for co in netstring.decode(model[2]): c, o = netstring.decode(co) # TODO? fix this self.pns_to_xml_unicode(resolved, question + (o, c, '_')) self.pns_contexts[c] = self.xml_parsed self.xml_parsed = None return False
def pns_to_xml_unicode (model, xml_types={}, xml_type=xml_dom.Element): name = unicode (model[1], 'utf-8') # try to decode the PNS/XML element try: attr, first, children, follow = netstring.validate ( model[2], 4 ) except: # if no PNS/XML element is encoded in the statement object, # consider the predicate as the element name, the object as # first CDATA and the subject as only attribute if it is # distinct from the statement's context. in effect, translate # *any* PNS statement to an XML element: # # <predicate pns="subject">object</predicate> # if model[0] != model[3]: attr = {u'pns': unicode (model[0], 'utf-8')} else: attr = None e = xml_types.get (name, xml_type) ( name or u'http://allegra/ pns-xml-error', attr ) if model[2]: e.xml_first = unicode (model[2], 'utf-8') else: e.xml_first = u'' return e, None # decode the attributes and set the pns attribute if attr: attr = dict (( tuple (( unicode (s, 'utf-8') for s in netstring.decode (item) )) for item in netstring.decode (attr) )) if model[0] != model[3]: attr[u'pns'] = unicode (model[0], 'utf-8') elif model[0] != model[3]: attr = {u'pns': unicode (model[0], 'utf-8')} else: attr = None # decode the name and instanciate an XML element e = xml_types.get (name, xml_type) (name, attr) if first: e.xml_first = unicode (first, 'utf-8') else: e.xml_first = u'' if follow: e.xml_follow = unicode (follow, 'utf-8') return e, children
def pns_to_xml_unicode_a (self, resolved, model): if model[4] != ('_') or model[2] == '': return False self.pns_contexts = {} question = resolved[:2] for co in netstring.decode (model[2]): c, o = netstring.decode (co) # TODO? fix this self.pns_to_xml_unicode ( resolved, question+(o, c, '_') ) self.pns_contexts[c] = self.xml_parsed self.xml_parsed = None return False
def pns_timeout (self, sp): if sp == self.PNS_SP: # protocol self.pns_tcp_continue (( self.pns_name, '', '', self.pns_name ), '_') if self.pns_statements.has_key (sp): # timed-out o = self.pns_statements.pop (sp) if o == '': if self.pns_right and self.pns_left: # quitted defered until joined self.pns_quit () else: # quitting self.pns_quitted () else: pass # joining return if self.pns_buffer.has_key (sp): # delete timeouted out buffered subject and predicate o = self.pns_buffer[sp] del self.pns_buffer[sp] else: o = '' # echo the non-protocol timeouts to all subscribers model = list (netstring.decode (sp)) model.append (o) model.append (self.pns_name) self.pns_tcp_continue (model, '_') # quit if timeouts on a statement if self.pns_statements.has_key (sp): self.pns_quit ()
def statement_utf8(model, prefixes): e, children = pns_to_xml_utf8(model) if children: e.xml_children = [] for child in netstring.decode(children): subject, name = netstring.decode(child) if subject: e.xml_children.append( '<%s pns="%s"/>' % (''.join(xml_utf8.xml_prefix_FQN( name, prefixes)), xml_utf8.xml_attr(subject))) else: e.xml_children.append('<%s />' % xml_utf8.xml_prefix_FQN(name, prefixes)) return xml_utf8.xml_prefixed(e, prefixes, ' context="%s"' % xml_utf8.xml_attr(model[3]))
def name_utf8(name, tag='public'): names = tuple(netstring.decode(name)) or (name, ) if len(names) > 1: return '<%s names="%s">%s</%s>' % (tag, xml_utf8.xml_attr( name), ''.join([name_utf8(n, tag) for n in names]), tag) return '<%s>%s</%s>' % (tag, xml_tf8.xml_cdata(name), tag)
def pns_articulate (self): try: encoded = self.pns_pipe.next () except StopIteration: # no more statements to handle, close self.async_net_push (('0:,0:,0:,0:,',)) # self.close_when_done () return model = list (netstring.decode (encoded)) if '' == model[0]: if '' == model[1] == model[2]: self.pns_quit (model[3]) else: self.pns_command (tuple (model[:3])) elif '' == model[1] and model[0] == model[3]: if model[2]: self.pns_join (model[3], model[2]) else: self.pns_subscribe (model[3]) else: self.pns_statement ( tuple (model[:3]), model[3] )
def name_unicode(name, tag='public', encoding='ASCII'): names = tuple(netstring.decode(name)) or (name, ) if len(names) > 1: return '<%s names="%s">%s</%s>' % ( tag, xml_unicode.xml_attr(unicode(name, 'UTF-8')), ''.join( [name_unicode(n, tag, encoding) for n in names]), tag) return '<%s>%s</%s>' % (tag, xml_unicode.xml_cdata(unicode(name, 'UTF-8')), tag) # Note about this implementation # # it may be possible to get rid of the last level of PNS/XML articulation # and make articulation sparser, by allowing un-named child element to # be completely encoded in their parent. Yet this practically makes encoding # from XML to PNS also more complex to support sensible trunking. # # Most XML leaf elements do not require a PNS/XML statement but the # presence of one too large in their midst suppose a more elaborated # algorithm for sparse articulation, counting every bytes in the parent's # PNS/XML statement to decide which elements to "inline" as: # # :subject,:name,::attributes,:first,:follow,, # # and which to reference only: # # :subject,:name,:, # # it just might not be worth the trouble. # # TODO: add a depth limit to PNS_XML, avoid the possible infinite loop # when there is a circular PNS/XML statement in the metabase for # the articulated XML element tree.
def pns_to_xml_unicode(model, xml_types={}, xml_type=xml_dom.Element): name = unicode(model[1], 'utf-8') # try to decode the PNS/XML element try: attr, first, children, follow = netstring.validate(model[2], 4) except: # if no PNS/XML element is encoded in the statement object, # consider the predicate as the element name, the object as # first CDATA and the subject as only attribute if it is # distinct from the statement's context. in effect, translate # *any* PNS statement to an XML element: # # <predicate pns="subject">object</predicate> # if model[0] != model[3]: attr = {u'pns': unicode(model[0], 'utf-8')} else: attr = None e = xml_types.get(name, xml_type)(name or u'http://allegra/ pns-xml-error', attr) if model[2]: e.xml_first = unicode(model[2], 'utf-8') else: e.xml_first = u'' return e, None # decode the attributes and set the pns attribute if attr: attr = dict((tuple((unicode(s, 'utf-8') for s in netstring.decode(item))) for item in netstring.decode(attr))) if model[0] != model[3]: attr[u'pns'] = unicode(model[0], 'utf-8') elif model[0] != model[3]: attr = {u'pns': unicode(model[0], 'utf-8')} else: attr = None # decode the name and instanciate an XML element e = xml_types.get(name, xml_type)(name, attr) if first: e.xml_first = unicode(first, 'utf-8') else: e.xml_first = u'' if follow: e.xml_follow = unicode(follow, 'utf-8') return e, children
def async_net_continue(self, encoded): "handle the following PNS/TCP peer statement" # Note that there is no validation of the PNS statements # because they are trusted to be valid. A client *may* # validate, the peer *must* anyway. # self.pns_received += 1 model = list(netstring.decode(encoded)) if len(model) != 5: assert None == self.log(encoded, 'invalid-peer-statement') self.handle_close() return self.pns_multiplex(model) resolved = tuple(model[:3]) if '' == model[0]: # close, index, context and route handlers = self.pns_commands.get(resolved) if handlers == None: assert None == self.log(encoded, 'unsollicited-command-response') self.handle_close() return self.pns_commands[resolved] = [ h for h in handlers if h(resolved, model) ] if len(self.pns_commands[resolved]) == 0: del self.pns_commands[resolved] elif '' == model[1] and model[0] == model[3]: # TODO: protocol response to join/subscribe pass else: # statements statements = self.pns_contexts.setdefault(model[3], {}) handlers = statements.get(resolved) if handlers == None: # test for a question's answer handler resolved = (model[0], model[1], '') handlers = statements.get(resolved) if handlers == None: # irrelevant statement self.pns_noise(model) return # relevant statements, handle and refresh the handlers list statements[resolved] = [h for h in handlers if h(resolved, model)] # then clean up ... if len(statements[resolved]) == 0: del statements[resolved] if len(statements) == 0: del self.pns_contexts[model[3]] if (self.pns_close_when_done and len(self.pns_commands) == 0 and len(self.pns_contexts) == 0 and len(self.pns_subscribed) == 0): assert None == self.log('done', 'debug') self.handle_close()
def pns_quatuor (encoded, contexts, max=1024): # a valid quatuor must have subject, predicate, object and context # assert type (max) == int and max % 2 == 0 model = list (netstring.decode (encoded)) if len (model) != 4: return None, '1 not a quatuor' if model[3]: # Validate the context as a public name, but allways check # the supplied cache of valid PNS names first! # if public_names.valid_in_utf8 (model[3], contexts) != None: return None, '2 invalid context' if model[0]: # the predicate length and subject length are limited to # or 512 bytes minus the ten needed to encode them # sp_len = len (model[0]) + len (model[1]) + len ( '%d%d' % (len (model[0]), len (model[1])) ) if sp_len > max/2: return None, '3 invalid statement length' # validate the statement subject as a public name if model[0] != model[3] and public_names.valid_in_utf8 ( subject, contexts ) == None: return None, '4 invalid subject' if model[2]: # non-empty objects are trunked to fit the 1024 bytes # PNS/UDP answer datagram. Sorry guys, but there is # no way to fit more and rationning the number of # statement per user *does* limit potential abuses. # model[2] = model[2][:( max - sp_len - len ('%d' % (max - sp_len)) )] # # this is not an error condition, its a welcome # feature of any PNS peer, the only "optional" # error handling that allows use agent to be sloppy # in their validation of PNS statements. simplistic # clients should be tolerated ;-) elif model[1]: if public_names.valid_in_utf8 (command, contexts) == None: return None, '5 invalid command predicate' elif model[2] and public_names.valid_in_utf8 ( model[2], contexts ) == None: return None, '6 invalid command object' return model, ''
def pns_to_xml_unicode(self, resolved, model): self.xml_parsed, children = pns_to_xml_unicode(model, self.pns_dom.xml_types, self.pns_dom.xml_type) if children: self.xml_parsed.xml_children = children = list( netstring.decode(children)) for child in children: subject, name = tuple(netstring.decode(child)) if subject: context = model[0] else: context = subject = model[0] joined = PNS_XML_continuation(self.pns_dom, child) joined.pns_context = model[3] self.pns_dom.pns_statement((subject, name, ''), context, joined.pns_to_xml_unicode) joined.finalization = self
def name_utf8 (name, tag='public'): names = tuple (netstring.decode (name)) or (name,) if len (names) > 1: return '<%s names="%s">%s</%s>' % ( tag, xml_utf8.xml_attr (name), ''.join ([name_utf8 (n, tag) for n in names]), tag ) return '<%s>%s</%s>' % (tag, xml_tf8.xml_cdata (name), tag)
def pns_quatuor(encoded, pns_names, PNS_LENGTH=1024): # a valid quatuor must have subject, predicate, object and context # model = list(netstring.decode(encoded)) if len(model) != 4: return None, '1 not a quatuor' if model[3]: # Validate the context as a public name, but allways check # the supplied cache of valid PNS names first! # if not (model[3] in pns_names or model[3] == pns_name(model[3], set())): return None, '2 invalid context' if model[0]: # the predicate length and subject length are limited to # or 512 bytes minus the ten needed to encode them # sp_len = len(model[0]) + len(model[1]) + len( '%d%d' % (len(model[0]), len(model[1]))) if sp_len > PNS_LENGTH / 2: return None, '3 invalid statement length' # validate the statement subject as a public name if model[0] != model[3] and not (model[0] in pns_names or model[0] == pns_name(model[0], set())): return None, '4 invalid subject' if model[2]: # non-empty objects are trunked to fit the 1024 bytes # PNS/UDP answer datagram. Sorry guys, but there is # no way to fit more and rationning the number of # statement per user *does* limit potential abuses. # model[2] = model[2][:(PNS_LENGTH - sp_len - len('%d' % (PNS_LENGTH - sp_len)))] # # this is not an error condition, its a welcome # feature of any PNS peer, the only "optional" # error handling that allows use agent to be sloppy # in their validation of PNS statements. simplistic # clients should be tolerated ;-) elif model[1]: if not (model[1] in pns_names or model[1] == pns_name(model[1], set())): return None, '5 invalid command predicate' elif model[2] and not (model[2] in pns_names or model[2] == pns_name(model[2], set())): return None, '6 invalid command object' return model, ''
def statement_unicode(model, prefixes, encoding='ASCII'): e, children = pns_to_xml_unicode(model) if children: e.xml_children = [] for child in netstring.decode(children): subject, name = netstring.decode(child) if subject: e.xml_children.append( '<%s pns="%s"/>' % (''.join( xml_unicode.xml_prefix_FQN(unicode(name, 'utf-8'), prefixes, encoding)), xml_unicode.xml_attr(unicode(subject, 'utf-8'), encoding))) else: e.xml_children.append( '<%s%s' ' />' % xml_unicode.xml_prefix_FQN(unicode(name, 'utf-8'), prefixes, encoding)) return xml_unicode.xml_prefixed( e, prefixes, ' context="%s"' % xml_unicode.xml_attr(unicode(model[3], 'utf-8'), encoding), encoding)
def pns_to_xml_utf8(self, resolved, model): if model[4][0] in ('.', '?'): return False self.xml_parsed, children = pns_to_xml_utf8(model, self.pns_dom.xml_types, self.pns_dom.xml_type) # decode the children's name and subject, if children: self.xml_parsed.xml_children = children = list( netstring.decode(children)) for child in children: subject, name = tuple(netstring.decode(child)) if subject: context = model[0] else: context = subject = model[0] joined = PNS_XML_continuation(self.pns_dom, child) self.pns_dom.pns_statement((subject, name, ''), context, joined.pns_to_xml_unicode) joined.finalization = self return False
def statement_utf8 (model, prefixes): e, children = pns_to_xml_utf8 (model) if children: e.xml_children = [] for child in netstring.decode (children): subject, name = netstring.decode (child) if subject: e.xml_children.append ('<%s pns="%s"/>' % ( ''.join (xml_utf8.xml_prefix_FQN ( name, prefixes )), xml_utf8.xml_attr (subject) )) else: e.xml_children.append ( '<%s />' % xml_utf8.xml_prefix_FQN ( name, prefixes ) ) return xml_utf8.xml_prefixed ( e, prefixes, ' context="%s"' % xml_utf8.xml_attr (model[3]) )
def pns_to_xml_utf8 (model, xml_types={}, xml_type=xml_dom.Element): # try to decode the PNS/XML element try: attr, first, children, follow = netstring.validate ( model[2], 4 ) except: if model[0] != model[3]: attr = {'pns': model[0]} else: attr = None e = xml_types.get (model[1], xml_type) ( model[1] or 'http://allegra/ pns-xml-error', attr ) e.xml_first = model[2] return e, None # decode the attributes and set the pns attribute if attr: attr = dict (( tuple (netstring.decode (item)) for item in netstring.decode (attr) )) if model[0] != model[3]: attr['pns'] = model[0] elif model[0] != model[3]: attr = {'pns': model[0]} else: attr = None e = xml_types.get (model[1], xml_type) (model[1], attr) if first: e.xml_first = first else: e.xml_first = '' if follow: e.xml_follow = follow return e, children
def handle_read (self): datagram, peer = self.recvfrom (pns_datagram_size) if peer == None: assert None == self.log ('nop', 'debug') return if self.pns_joined.has_key (peer): # in circle question ... self.pns_joined[peer].pns_question (datagram, peer) return if self.pns_accepted.has_key (peer[0]): # question from a new peer accepted at right self.pns_accepted[peer[0]].pns_question ( datagram, peer ) return # out of circle model = list (netstring.decode (datagram)) if ( len (model) != 2 or # TODO: ? 3 instead ? model[0] == '' or not public_names.valid_utf8 (model[0]) ): # log and drop invalid out-of-circle question ... self.log ( 'invalid-peer ip="%s"' % peer[0], 'error' ) return # if subscribed, joined ... if self.pns_peer.pns_subscribed.has_key (model[0]): self.pns_peer.pns_subscribed[ model[0] ].pns_joined (peer) return # not subscribed, resolve a PIRP answer >>> if not self.pns_peers.has_key (peer): self.pns_peer.pns_resolution.pns_udp_pirp ( model[0], peer )
def pns_resolve_context (self, resolved, model): if model != None: # context(s) found self.pns_walk_out (resolved[1], model[1]) return # stop walking. # no context known for name in tuple (netstring.decode (resolved[1])): if name in self.pns_walked: continue if len (self.pns_walked) >= self.PNS_HORIZON: break # beyond the horizon, stop walking. # new and below the horizon self.pns_walked.add (name) self.pns_articulator.pns_command ( ('', '', name), self.pns_resolve_index ) # walk up the indexes ...
def pns_resolve_index (self, resolved, model): if model != None: # known name name = model[0] if ( len (self.pns_walked) > self.PNS_HORIZON or name in self.pns_walked ): return # new and below the horizon self.pns_walked.add (name) self.pns_articulator.pns_command ( ('', name, ''), self.pns_resolve_context ) # walk the contexts ... # unknown name names = tuple (netstring.decode (resolved[2])) if len (names) == 0: # inarticulated self.pns_articulator.pns_command ( ('', name, ''), self.pns_resolve_context ) # walk the contexts ... return # for each name articulated for name in names: if name in self.pns_walked: continue if len (self.pns_walked) > self.PNS_HORIZON: break # new and below the horizon self.pns_walked.add (name) self.pns_articulator.pns_command ( ('', name, ''), self.pns_resolve_context ) # walk the contexts ...
def name_unicode (name, tag='public', encoding='ASCII'): names = tuple (netstring.decode (name)) or (name,) if len (names) > 1: return '<%s names="%s">%s</%s>' % ( tag, xml_unicode.xml_attr (unicode (name, 'UTF-8')), ''.join ([name_unicode ( n, tag, encoding ) for n in names]), tag ) return '<%s>%s</%s>' % ( tag, xml_unicode.xml_cdata (unicode (name, 'UTF-8')), tag ) # Note about this implementation # # it may be possible to get rid of the last level of PNS/XML articulation # and make articulation sparser, by allowing un-named child element to # be completely encoded in their parent. Yet this practically makes encoding # from XML to PNS also more complex to support sensible trunking. # # Most XML leaf elements do not require a PNS/XML statement but the # presence of one too large in their midst suppose a more elaborated # algorithm for sparse articulation, counting every bytes in the parent's # PNS/XML statement to decide which elements to "inline" as: # # :subject,:name,::attributes,:first,:follow,, # # and which to reference only: # # :subject,:name,:, # # it just might not be worth the trouble. # # TODO: add a depth limit to PNS_XML, avoid the possible infinite loop # when there is a circular PNS/XML statement in the metabase for # the articulated XML element tree.
def pns_articulate(self): try: encoded = self.pns_pipe.next() except StopIteration: # no more statements to handle, close self.async_net_push(('0:,0:,0:,0:,', )) # self.close_when_done () return model = list(netstring.decode(encoded)) if '' == model[0]: if '' == model[1] == model[2]: self.pns_quit(model[3]) else: self.pns_command(tuple(model[:3])) elif '' == model[1] and model[0] == model[3]: if model[2]: self.pns_join(model[3], model[2]) else: self.pns_subscribe(model[3]) else: self.pns_statement(tuple(model[:3]), model[3])
def pns_name(encoded, horizon, HORIZON=126): # Recursively validate a Public Name, returns the empty string if # the name encoded is invalid or inside the horizon. This function # does more than just assert that the encoded 8-bit byte string is # a valid public name: it transform it to a valid public name. # # try to decode the articulated public names names = [n for n in netstring.decode(encoded) if n] if not names: if encoded not in horizon and pns_name_clean(encoded): # clean name new in this horizon horizon.add(encoded) return encoded # unsafe 8-bit byte string or Public Name in the in horizon return '' # articulated Public Names valid = [] for name in names: # recursively validate each articulated name in this horizon name = pns_name(name, horizon, HORIZON) if name: valid.append(name) if len(horizon) >= HORIZON: break # but only under this HORIZON if len(valid) > 1: # sort Public Names and encode valid.sort() return netstring.encode(valid) if len(valid) > 0: # return a "singleton" return valid[0] return '' # nothing valid to articulate in this horizon
def pns_name (encoded, horizon, HORIZON=126): # Recursively validate a Public Name, returns the empty string if # the name encoded is invalid or inside the horizon. This function # does more than just assert that the encoded 8-bit byte string is # a valid public name: it transform it to a valid public name. # # try to decode the articulated public names names = [n for n in netstring.decode (encoded) if n] if not names: if encoded not in horizon and pns_name_clean (encoded): # clean name new in this horizon horizon.add (encoded) return encoded # unsafe 8-bit byte string or Public Name in the in horizon return '' # articulated Public Names valid = [] for name in names: # recursively validate each articulated name in this horizon name = pns_name (name, horizon, HORIZON) if name: valid.append (name) if len (horizon) >= HORIZON: break # but only under this HORIZON if len (valid) > 1: # sort Public Names and encode valid.sort () return netstring.encode (valid) if len (valid) > 0: # return a "singleton" return valid[0] return '' # nothing valid to articulate in this horizon
def pns_question (self, datagram, peer): # handle in-circle question if datagram.startswith ('0:,'): # command: quit right model = list (netstring.decode (datagram)) if len (model) == 2 and ip_peer.is_ip (model[1]): self.pns_quit_right (model[1]) return assert None == self.log ( datagram, 'invalid-protocol-command' ) self.pns_quitted () return if datagram.startswith (self.PNS_SP): # protocol question for this circle if self.pns_right: # in-circle, forward quit and then close self.sendto (self.PNS_SP, ( self.pns_left, 3534 )) self.pns_quitted () elif self.pns_left: # ? joining pass else: # ? accept pass return # validate question model = list (netstring.decode (datagram)) if (len (model) != 2 or not public_names.valid_utf8 ( model[0], self.pns_horizon )): assert None == self.log ( datagram, 'invalid-question' ) # TODO: ban IP addresses that send invalid datagrams! self.pns_quit () return sp = netstring.encode (model) if ( self.pns_buffer.get (sp) == '' and not self.pns_statements.has_key (sp) ): # buffered not stated, drop assert None == self.log (datagram, 'drop') return # echo the statement to the PNS/TCP subscribers model.append ('') model.append (self.pns_name) self.pns_tcp_continue (model, '?') if self.pns_statements.get (sp) == '': # question circled, clear the statement, do not relay del self.pns_statements[sp] assert None == self.log (datagram, 'circle') return # relay left self.sendto (sp, (self.pns_left, 3534)) if ( model[1] == '' and self.pns_peer.pns_subscribed.has_key (model[0]) ): # protocol question for a subscribed circle, bounce # this peer's IP address as the answer. left = self.pns_peer.pns_udp.addr[0] self.pns_sendto_right ( '%s%d:%s' % (sp, len(left), left), self.pns_right ) return # resolve ... self.pns_peer.pns_resolution.pns_udp_question (model)
def pns_answer (self, datagram, peer): # handle in-circle and out-of-circle answers if datagram.startswith ('0:,'): if not self.pns_right: # out-of-circle model = list (netstring.decode (datagram)) if ( len (model) == 3 and model[1] == '' and ip_peer.is_ip (model[2]) ): # bounce to join left or right of L self.pns_join (model[2]) return assert None == self.log ( datagram, 'invalid-command-answer' ) self.pns_quit () return if datagram.startswith (self.PNS_SP): # handle protocol statement model = list (netstring.decode (datagram)) if not (len (model) == 3 and ( model[2] == '' or ip_peer.is_ip (model[2]) )): assert None == self.log ( datagram, 'invalid-protocol-answer' ) self.pns_quit () return if self.pns_right: # in-circle, quitted at left! return # out-of-circle if model[2]: # accept questions from R self.pns_peer.pns_udp.pns_accepted[ model[2] ] = self else: # root join self.pns_in_circle (peer) return # validate answer model = list (netstring.decode (datagram)) if ( (len (model[0]) + len (model[1]) + len ( '%d%d' % (len (model[0]), len (model[1])) )) > 512 or not public_names.valid_utf8 ( model[0], self.pns_horizon ) ): assert None == self.log ( datagram, 'invalid-question' ) self.pns_quit () return sp = netstring.encode (model[:2]) if ( self.pns_buffer.has_key (sp) and not self.pns_statements.has_key (sp) ): # buffered not stated, drop assert None == self.log (datagram, 'drop') return # echo to all subscribers model.append (self.pns_name) self.pns_tcp_continue (model, '!') if self.pns_statements.get (sp) == model[2]: # answer circled, clear the statement, do not relay del self.pns_statements [sp] assert None == self.log (datagram, 'circle') return # relay right self.pns_sendto_right ( netstring.encode (model), self.pns_right ) if ( model[1] == '' and self.pns_peer.pns_subscribed.has_key (model[0]) ): # protocol answer, let the named circle handle it if self.pns_peer.self.pns_peer.pns_subscribed[ model[0] ].pns_protocol_answer (model[2]): return # resolve ... self.pns_peer.pns_resolution.pns_udp_answer (model)
def async_net_continue (self, encoded): "handle the following PNS/TCP peer statement" # Note that there is no validation of the PNS statements # because they are trusted to be valid. A client *may* # validate, the peer *must* anyway. # self.pns_received += 1 model = list (netstring.decode (encoded)) if len (model) != 5: assert None == self.log ( encoded, 'invalid-peer-statement' ) self.handle_close () return self.pns_multiplex (model) resolved = tuple (model[:3]) if '' == model[0]: # close, index, context and route handlers = self.pns_commands.get (resolved) if handlers == None: assert None == self.log ( encoded, 'unsollicited-command-response' ) self.handle_close () return self.pns_commands[resolved] = [ h for h in handlers if h (resolved, model) ] if len (self.pns_commands[resolved]) == 0: del self.pns_commands[resolved] elif '' == model[1] and model[0] == model[3]: # TODO: protocol response to join/subscribe pass else: # statements statements = self.pns_contexts.setdefault ( model[3], {} ) handlers = statements.get (resolved) if handlers == None: # test for a question's answer handler resolved = (model[0], model[1], '') handlers = statements.get (resolved) if handlers == None: # irrelevant statement self.pns_noise (model) return # relevant statements, handle and refresh the handlers list statements[resolved] = [ h for h in handlers if h (resolved, model) ] # then clean up ... if len (statements[resolved]) == 0: del statements[resolved] if len (statements) == 0: del self.pns_contexts[model[3]] if ( self.pns_close_when_done and len (self.pns_commands) == 0 and len (self.pns_contexts) == 0 and len (self.pns_subscribed) == 0 ): assert None == self.log ('done', 'debug') self.handle_close ()