mercurial/wireprototypes.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43089 c59eb1560c44
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    17 )
    17 )
    18 from .interfaces import util as interfaceutil
    18 from .interfaces import util as interfaceutil
    19 from .utils import compression
    19 from .utils import compression
    20 
    20 
    21 # Names of the SSH protocol implementations.
    21 # Names of the SSH protocol implementations.
    22 SSHV1 = 'ssh-v1'
    22 SSHV1 = b'ssh-v1'
    23 # These are advertised over the wire. Increment the counters at the end
    23 # These are advertised over the wire. Increment the counters at the end
    24 # to reflect BC breakages.
    24 # to reflect BC breakages.
    25 SSHV2 = 'exp-ssh-v2-0003'
    25 SSHV2 = b'exp-ssh-v2-0003'
    26 HTTP_WIREPROTO_V2 = 'exp-http-v2-0003'
    26 HTTP_WIREPROTO_V2 = b'exp-http-v2-0003'
    27 
    27 
    28 NARROWCAP = 'exp-narrow-1'
    28 NARROWCAP = b'exp-narrow-1'
    29 ELLIPSESCAP1 = 'exp-ellipses-1'
    29 ELLIPSESCAP1 = b'exp-ellipses-1'
    30 ELLIPSESCAP = 'exp-ellipses-2'
    30 ELLIPSESCAP = b'exp-ellipses-2'
    31 SUPPORTED_ELLIPSESCAP = (ELLIPSESCAP1, ELLIPSESCAP)
    31 SUPPORTED_ELLIPSESCAP = (ELLIPSESCAP1, ELLIPSESCAP)
    32 
    32 
    33 # All available wire protocol transports.
    33 # All available wire protocol transports.
    34 TRANSPORTS = {
    34 TRANSPORTS = {
    35     SSHV1: {'transport': 'ssh', 'version': 1,},
    35     SSHV1: {b'transport': b'ssh', b'version': 1,},
    36     SSHV2: {
    36     SSHV2: {
    37         'transport': 'ssh',
    37         b'transport': b'ssh',
    38         # TODO mark as version 2 once all commands are implemented.
    38         # TODO mark as version 2 once all commands are implemented.
    39         'version': 1,
    39         b'version': 1,
    40     },
    40     },
    41     'http-v1': {'transport': 'http', 'version': 1,},
    41     b'http-v1': {b'transport': b'http', b'version': 1,},
    42     HTTP_WIREPROTO_V2: {'transport': 'http', 'version': 2,},
    42     HTTP_WIREPROTO_V2: {b'transport': b'http', b'version': 2,},
    43 }
    43 }
    44 
    44 
    45 
    45 
    46 class bytesresponse(object):
    46 class bytesresponse(object):
    47     """A wire protocol response consisting of raw bytes."""
    47     """A wire protocol response consisting of raw bytes."""
   114     def __init__(self, gen=None):
   114     def __init__(self, gen=None):
   115         self.gen = gen
   115         self.gen = gen
   116 
   116 
   117 
   117 
   118 # list of nodes encoding / decoding
   118 # list of nodes encoding / decoding
   119 def decodelist(l, sep=' '):
   119 def decodelist(l, sep=b' '):
   120     if l:
   120     if l:
   121         return [bin(v) for v in l.split(sep)]
   121         return [bin(v) for v in l.split(sep)]
   122     return []
   122     return []
   123 
   123 
   124 
   124 
   125 def encodelist(l, sep=' '):
   125 def encodelist(l, sep=b' '):
   126     try:
   126     try:
   127         return sep.join(map(hex, l))
   127         return sep.join(map(hex, l))
   128     except TypeError:
   128     except TypeError:
   129         raise
   129         raise
   130 
   130 
   132 # batched call argument encoding
   132 # batched call argument encoding
   133 
   133 
   134 
   134 
   135 def escapebatcharg(plain):
   135 def escapebatcharg(plain):
   136     return (
   136     return (
   137         plain.replace(':', ':c')
   137         plain.replace(b':', b':c')
   138         .replace(',', ':o')
   138         .replace(b',', b':o')
   139         .replace(';', ':s')
   139         .replace(b';', b':s')
   140         .replace('=', ':e')
   140         .replace(b'=', b':e')
   141     )
   141     )
   142 
   142 
   143 
   143 
   144 def unescapebatcharg(escaped):
   144 def unescapebatcharg(escaped):
   145     return (
   145     return (
   146         escaped.replace(':e', '=')
   146         escaped.replace(b':e', b'=')
   147         .replace(':s', ';')
   147         .replace(b':s', b';')
   148         .replace(':o', ',')
   148         .replace(b':o', b',')
   149         .replace(':c', ':')
   149         .replace(b':c', b':')
   150     )
   150     )
   151 
   151 
   152 
   152 
   153 # mapping of options accepted by getbundle and their types
   153 # mapping of options accepted by getbundle and their types
   154 #
   154 #
   160 # :nodes: list of binary nodes, transmitted as space-separated hex nodes
   160 # :nodes: list of binary nodes, transmitted as space-separated hex nodes
   161 # :csv:   list of values, transmitted as comma-separated values
   161 # :csv:   list of values, transmitted as comma-separated values
   162 # :scsv:  set of values, transmitted as comma-separated values
   162 # :scsv:  set of values, transmitted as comma-separated values
   163 # :plain: string with no transformation needed.
   163 # :plain: string with no transformation needed.
   164 GETBUNDLE_ARGUMENTS = {
   164 GETBUNDLE_ARGUMENTS = {
   165     'heads': 'nodes',
   165     b'heads': b'nodes',
   166     'bookmarks': 'boolean',
   166     b'bookmarks': b'boolean',
   167     'common': 'nodes',
   167     b'common': b'nodes',
   168     'obsmarkers': 'boolean',
   168     b'obsmarkers': b'boolean',
   169     'phases': 'boolean',
   169     b'phases': b'boolean',
   170     'bundlecaps': 'scsv',
   170     b'bundlecaps': b'scsv',
   171     'listkeys': 'csv',
   171     b'listkeys': b'csv',
   172     'cg': 'boolean',
   172     b'cg': b'boolean',
   173     'cbattempted': 'boolean',
   173     b'cbattempted': b'boolean',
   174     'stream': 'boolean',
   174     b'stream': b'boolean',
   175     'includepats': 'csv',
   175     b'includepats': b'csv',
   176     'excludepats': 'csv',
   176     b'excludepats': b'csv',
   177 }
   177 }
   178 
   178 
   179 
   179 
   180 class baseprotocolhandler(interfaceutil.Interface):
   180 class baseprotocolhandler(interfaceutil.Interface):
   181     """Abstract base class for wire protocol handlers.
   181     """Abstract base class for wire protocol handlers.
   251     """Represents a declared wire protocol command."""
   251     """Represents a declared wire protocol command."""
   252 
   252 
   253     def __init__(
   253     def __init__(
   254         self,
   254         self,
   255         func,
   255         func,
   256         args='',
   256         args=b'',
   257         transports=None,
   257         transports=None,
   258         permission='push',
   258         permission=b'push',
   259         cachekeyfn=None,
   259         cachekeyfn=None,
   260         extracapabilitiesfn=None,
   260         extracapabilitiesfn=None,
   261     ):
   261     ):
   262         self.func = func
   262         self.func = func
   263         self.args = args
   263         self.args = args
   290         if i == 0:
   290         if i == 0:
   291             return self.func
   291             return self.func
   292         elif i == 1:
   292         elif i == 1:
   293             return self.args
   293             return self.args
   294         else:
   294         else:
   295             raise IndexError('can only access elements 0 and 1')
   295             raise IndexError(b'can only access elements 0 and 1')
   296 
   296 
   297 
   297 
   298 class commanddict(dict):
   298 class commanddict(dict):
   299     """Container for registered wire protocol commands.
   299     """Container for registered wire protocol commands.
   300 
   300 
   306         if isinstance(v, commandentry):
   306         if isinstance(v, commandentry):
   307             pass
   307             pass
   308         # Cast 2-tuples to commandentry instances.
   308         # Cast 2-tuples to commandentry instances.
   309         elif isinstance(v, tuple):
   309         elif isinstance(v, tuple):
   310             if len(v) != 2:
   310             if len(v) != 2:
   311                 raise ValueError('command tuples must have exactly 2 elements')
   311                 raise ValueError(b'command tuples must have exactly 2 elements')
   312 
   312 
   313             # It is common for extensions to wrap wire protocol commands via
   313             # It is common for extensions to wrap wire protocol commands via
   314             # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
   314             # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
   315             # doing this aren't aware of the new API that uses objects to store
   315             # doing this aren't aware of the new API that uses objects to store
   316             # command entries, we automatically merge old state with new.
   316             # command entries, we automatically merge old state with new.
   320                 # Use default values from @wireprotocommand.
   320                 # Use default values from @wireprotocommand.
   321                 v = commandentry(
   321                 v = commandentry(
   322                     v[0],
   322                     v[0],
   323                     args=v[1],
   323                     args=v[1],
   324                     transports=set(TRANSPORTS),
   324                     transports=set(TRANSPORTS),
   325                     permission='push',
   325                     permission=b'push',
   326                 )
   326                 )
   327         else:
   327         else:
   328             raise ValueError(
   328             raise ValueError(
   329                 'command entries must be commandentry instances ' 'or 2-tuples'
   329                 b'command entries must be commandentry instances '
       
   330                 b'or 2-tuples'
   330             )
   331             )
   331 
   332 
   332         return super(commanddict, self).__setitem__(k, v)
   333         return super(commanddict, self).__setitem__(k, v)
   333 
   334 
   334     def commandavailable(self, command, proto):
   335     def commandavailable(self, command, proto):
   352 
   353 
   353     compengines = compression.compengines.supportedwireengines(role)
   354     compengines = compression.compengines.supportedwireengines(role)
   354 
   355 
   355     # Allow config to override default list and ordering.
   356     # Allow config to override default list and ordering.
   356     if role == compression.SERVERROLE:
   357     if role == compression.SERVERROLE:
   357         configengines = ui.configlist('server', 'compressionengines')
   358         configengines = ui.configlist(b'server', b'compressionengines')
   358         config = 'server.compressionengines'
   359         config = b'server.compressionengines'
   359     else:
   360     else:
   360         # This is currently implemented mainly to facilitate testing. In most
   361         # This is currently implemented mainly to facilitate testing. In most
   361         # cases, the server should be in charge of choosing a compression engine
   362         # cases, the server should be in charge of choosing a compression engine
   362         # because a server has the most to lose from a sub-optimal choice. (e.g.
   363         # because a server has the most to lose from a sub-optimal choice. (e.g.
   363         # CPU DoS due to an expensive engine or a network DoS due to poor
   364         # CPU DoS due to an expensive engine or a network DoS due to poor
   364         # compression ratio).
   365         # compression ratio).
   365         configengines = ui.configlist(
   366         configengines = ui.configlist(
   366             'experimental', 'clientcompressionengines'
   367             b'experimental', b'clientcompressionengines'
   367         )
   368         )
   368         config = 'experimental.clientcompressionengines'
   369         config = b'experimental.clientcompressionengines'
   369 
   370 
   370     # No explicit config. Filter out the ones that aren't supposed to be
   371     # No explicit config. Filter out the ones that aren't supposed to be
   371     # advertised and return default ordering.
   372     # advertised and return default ordering.
   372     if not configengines:
   373     if not configengines:
   373         attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
   374         attr = (
       
   375             b'serverpriority' if role == util.SERVERROLE else b'clientpriority'
       
   376         )
   374         return [
   377         return [
   375             e for e in compengines if getattr(e.wireprotosupport(), attr) > 0
   378             e for e in compengines if getattr(e.wireprotosupport(), attr) > 0
   376         ]
   379         ]
   377 
   380 
   378     # If compression engines are listed in the config, assume there is a good
   381     # If compression engines are listed in the config, assume there is a good
   381     # unusable compression engines.
   384     # unusable compression engines.
   382     validnames = set(e.name() for e in compengines)
   385     validnames = set(e.name() for e in compengines)
   383     invalidnames = set(e for e in configengines if e not in validnames)
   386     invalidnames = set(e for e in configengines if e not in validnames)
   384     if invalidnames:
   387     if invalidnames:
   385         raise error.Abort(
   388         raise error.Abort(
   386             _('invalid compression engine defined in %s: %s')
   389             _(b'invalid compression engine defined in %s: %s')
   387             % (config, ', '.join(sorted(invalidnames)))
   390             % (config, b', '.join(sorted(invalidnames)))
   388         )
   391         )
   389 
   392 
   390     compengines = [e for e in compengines if e.name() in configengines]
   393     compengines = [e for e in compengines if e.name() in configengines]
   391     compengines = sorted(
   394     compengines = sorted(
   392         compengines, key=lambda e: configengines.index(e.name())
   395         compengines, key=lambda e: configengines.index(e.name())
   393     )
   396     )
   394 
   397 
   395     if not compengines:
   398     if not compengines:
   396         raise error.Abort(
   399         raise error.Abort(
   397             _(
   400             _(
   398                 '%s config option does not specify any known '
   401                 b'%s config option does not specify any known '
   399                 'compression engines'
   402                 b'compression engines'
   400             )
   403             )
   401             % config,
   404             % config,
   402             hint=_('usable compression engines: %s') % ', '.sorted(validnames),
   405             hint=_(b'usable compression engines: %s')
       
   406             % b', '.sorted(validnames),
   403         )
   407         )
   404 
   408 
   405     return compengines
   409     return compengines
   406 
   410 
   407 
   411