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. |
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 |