Mercurial > hg-stable
changeset 36403:b8d0761a85c7
wireproto: document the wonky push protocol for SSH
It took me several minutes to figure out how the "unbundle"
protocol worked. It turns out that the SSH protocol handler
sends an empty reply that is interpreted as "OK to send" and
only then does the client send the bundle payload.
On top of that, the response is different depending on whether
the operation was successful or not. I nearly pulled out my hair
deciphering this.
Differential Revision: https://phab.mercurial-scm.org/D2385
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Wed, 21 Feb 2018 16:47:39 -0800 |
parents | 0c231df1ffdc |
children | 0147a4730420 |
files | mercurial/sshpeer.py mercurial/wireprotoserver.py |
diffstat | 2 files changed, 20 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/sshpeer.py Wed Feb 21 14:21:05 2018 -0800 +++ b/mercurial/sshpeer.py Wed Feb 21 16:47:39 2018 -0800 @@ -466,25 +466,40 @@ return self._sendrequest(cmd, args, framed=True).read() def _callpush(self, cmd, fp, **args): + # The server responds with an empty frame if the client should + # continue submitting the payload. r = self._call(cmd, **args) if r: return '', r + + # The payload consists of frames with content followed by an empty + # frame. for d in iter(lambda: fp.read(4096), ''): self._writeframed(d) self._writeframed("", flush=True) + + # In case of success, there is an empty frame and a frame containing + # the integer result (as a string). + # In case of error, there is a non-empty frame containing the error. r = self._readframed() if r: return '', r return self._readframed(), '' def _calltwowaystream(self, cmd, fp, **args): + # The server responds with an empty frame if the client should + # continue submitting the payload. r = self._call(cmd, **args) if r: # XXX needs to be made better raise error.Abort(_('unexpected remote reply: %s') % r) + + # The payload consists of frames with content followed by an empty + # frame. for d in iter(lambda: fp.read(4096), ''): self._writeframed(d) self._writeframed("", flush=True) + return self._pipei def _getamount(self):
--- a/mercurial/wireprotoserver.py Wed Feb 21 14:21:05 2018 -0800 +++ b/mercurial/wireprotoserver.py Wed Feb 21 16:47:39 2018 -0800 @@ -347,12 +347,16 @@ return [data[k] for k in keys] def forwardpayload(self, fpout): + # We initially send an empty response. This tells the client it is + # OK to start sending data. If a client sees any other response, it + # interprets it as an error. + _sshv1respondbytes(self._fout, b'') + # The file is in the form: # # <chunk size>\n<chunk> # ... # 0\n - _sshv1respondbytes(self._fout, b'') count = int(self._fin.readline()) while count: fpout.write(self._fin.read(count))