def _parse_dirreq_line(keyword, recognized_counts_attr, unrecognized_counts_attr, descriptor, entries): value = _value(keyword, entries) recognized_counts = {} unrecognized_counts = {} is_response_stats = keyword in ('dirreq-v2-resp', 'dirreq-v3-resp') key_set = DirResponse if is_response_stats else DirStat key_type = 'STATUS' if is_response_stats else 'STAT' for status, count in _mappings_for(keyword, value, divider=','): if not count.isdigit(): raise ValueError( '%s lines should contain %s=COUNT mappings: %s %s' % (keyword, key_type, keyword, value)) if status in key_set: recognized_counts[status] = int(count) else: unrecognized_counts[status] = int(count) setattr(descriptor, recognized_counts_attr, recognized_counts) setattr(descriptor, unrecognized_counts_attr, unrecognized_counts)
def _parse_hs_stats(keyword, stat_attribute, extra_attribute, descriptor, entries): # "<keyword>" num key=val key=val... value, stat, extra = _value(keyword, entries), None, {} if value is None: pass # not in the descriptor elif value == '': raise ValueError("'%s' line was blank" % keyword) else: if ' ' in value: stat_value, remainder = value.split(' ', 1) else: stat_value, remainder = value, None try: stat = int(stat_value) except ValueError: raise ValueError("'%s' stat was non-numeric (%s): %s %s" % (keyword, stat_value, keyword, value)) for key, val in _mappings_for(keyword, remainder): extra[key] = val setattr(descriptor, stat_attribute, stat) setattr(descriptor, extra_attribute, extra)
def _parse_body(descriptor: 'stem.descriptor.Descriptor', entries: ENTRY_TYPE) -> None: # In version 1.0.0 the body is everything after the first line. Otherwise # it's everything after the header's divider. content = io.BytesIO(descriptor.get_bytes()) if descriptor.version == '1.0.0': content.readline() # skip the first line else: while content.readline().strip() not in ('', HEADER_DIV, HEADER_DIV_ALT): pass # skip the header measurements = {} for line_bytes in content.readlines(): line = stem.util.str_tools._to_unicode(line_bytes.strip()) attr = dict(_mappings_for('measurement', line)) fingerprint = attr.get('node_id', '').lstrip( '$') # bwauths prefix fingerprints with '$' if not fingerprint: raise ValueError("Every meaurement must include 'node_id': %s" % stem.util.str_tools._to_unicode(line)) elif fingerprint in measurements: raise ValueError( 'Relay %s is listed multiple times. It should only be present once.' % fingerprint) measurements[fingerprint] = attr descriptor.measurements = measurements
def _parse_bridge_ip_transports_line(descriptor, entries): value, ip_transports = _value('bridge-ip-transports', entries), {} for protocol, count in _mappings_for('bridge-ip-transports', value, divider = ','): if not count.isdigit(): raise stem.ProtocolError('Transport count was non-numeric (%s): bridge-ip-transports %s' % (count, value)) ip_transports[protocol] = int(count) descriptor.ip_transports = ip_transports
def _parse_bridge_ip_versions_line(descriptor: 'stem.descriptor.Descriptor', entries: ENTRY_TYPE) -> None: value, ip_versions = _value('bridge-ip-versions', entries), {} for protocol, count in _mappings_for('bridge-ip-versions', value, divider = ','): if not count.isdigit(): raise stem.ProtocolError('IP protocol count was non-numeric (%s): bridge-ip-versions %s' % (count, value)) ip_versions[protocol] = int(count) descriptor.ip_versions = ip_versions
def _parse_port_count_line(keyword, attribute, descriptor, entries): # "<keyword>" port=N,port=N,... value, port_mappings = _value(keyword, entries), {} for port, stat in _mappings_for(keyword, value, divider = ','): if (port != 'other' and not stem.util.connection.is_valid_port(port)) or not stat.isdigit(): raise ValueError('Entries in %s line should only be PORT=N entries: %s %s' % (keyword, keyword, value)) port = int(port) if port.isdigit() else port port_mappings[port] = int(stat) setattr(descriptor, attribute, port_mappings)
def _parse_padding_counts_line(descriptor, entries): # "padding-counts" YYYY-MM-DD HH:MM:SS (NSEC s) key=val key=val... value = _value('padding-counts', entries) timestamp, interval, remainder = _parse_timestamp_and_interval('padding-counts', value) counts = {} for k, v in _mappings_for('padding-counts', remainder, require_value = True): counts[k] = int(v) if v.isdigit() else v setattr(descriptor, 'padding_counts_end', timestamp) setattr(descriptor, 'padding_counts_interval', interval) setattr(descriptor, 'padding_counts', counts)
def _parse_port_count_line(keyword: str, attribute: str, descriptor: 'stem.descriptor.Descriptor', entries: ENTRY_TYPE) -> None: # "<keyword>" port=N,port=N,... value, port_mappings = _value(keyword, entries), {} for port, stat in _mappings_for(keyword, value, divider = ','): if (port != 'other' and not stem.util.connection.is_valid_port(port)) or not stat.isdigit(): raise ValueError('Entries in %s line should only be PORT=N entries: %s %s' % (keyword, keyword, value)) port = int(port) if port.isdigit() else port # type: ignore # this can be an int or 'other' port_mappings[port] = int(stat) setattr(descriptor, attribute, port_mappings)
def _parse_geoip_to_count_line(keyword, attribute, descriptor, entries): # "<keyword>" CC=N,CC=N,... # # The maxmind geoip (https://www.maxmind.com/app/iso3166) has numeric # locale codes for some special values, for instance... # A1,"Anonymous Proxy" # A2,"Satellite Provider" # ??,"Unknown" value, locale_usage = _value(keyword, entries), {} for locale, count in _mappings_for(keyword, value, divider = ','): if not _locale_re.match(locale) or not count.isdigit(): raise ValueError('Entries in %s line should only be CC=N entries: %s %s' % (keyword, keyword, value)) locale_usage[locale] = int(count) setattr(descriptor, attribute, locale_usage)