diff --git a/nvim/msgpack_rpc_stream.lua b/nvim/msgpack_rpc_stream.lua index 667130b..8f61d32 100644 --- a/nvim/msgpack_rpc_stream.lua +++ b/nvim/msgpack_rpc_stream.lua @@ -11,6 +11,31 @@ local Tabpage = {} Tabpage.__index = Tabpage function Tabpage.new(id) return setmetatable({id=id}, Tabpage) end +local function hexdump(str) + local len = string.len(str) + local dump = "" + local hex = "" + local asc = "" + + for i = 1, len do + if 1 == i % 8 then + dump = dump .. hex .. asc .. "\n" + hex = string.format("%04x: ", i - 1) + asc = "" + end + + local ord = string.byte(str, i) + hex = hex .. string.format("%02x ", ord) + if ord >= 32 and ord <= 126 then + asc = asc .. string.char(ord) + else + asc = asc .. "." + end + end + + return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc +end + local Response = {} Response.__index = Response @@ -39,6 +64,7 @@ MsgpackRpcStream.__index = MsgpackRpcStream function MsgpackRpcStream.new(stream) return setmetatable({ _stream = stream, + _previous_chunk = nil, _pack = mpack.Packer({ ext = { [Buffer] = function(o) return 0, mpack.pack(o.id) end, @@ -76,20 +102,38 @@ function MsgpackRpcStream:read_start(request_cb, notification_cb, eof_cb) if not data then return eof_cb() end - local type, id_or_cb + local status, type_, id_or_cb local pos = 1 local len = #data while pos <= len do - type, id_or_cb, method_or_error, args_or_result, pos = - self._session:receive(data, pos) - if type == 'request' or type == 'notification' then - if type == 'request' then + -- grab a copy of pos since pcall() will set it to nil on error + local oldpos = pos + status, type_, id_or_cb, method_or_error, args_or_result, pos = pcall( + self._session.receive, self._session, data, pos) + if not status then + -- write the full blob of bad data to a specific file + local outfile = io.open('./msgpack-invalid-data', 'w') + outfile:write(data) + outfile:close() + + -- build a printable representation of the bad part of the string + local printable = hexdump(data:sub(oldpos, oldpos + 8 * 10)) + + print(string.format("Error deserialising msgpack data stream at pos %d:\n%s\n", + oldpos, printable)) + print(string.format("... occurred after %s", self._previous_chunk)) + error(type_) + end + if type_ == 'request' or type_ == 'notification' then + self._previous_chunk = string.format('%s<%s>', type_, method_or_error) + if type_ == 'request' then request_cb(method_or_error, args_or_result, Response.new(self, id_or_cb)) else notification_cb(method_or_error, args_or_result) end - elseif type == 'response' then + elseif type_ == 'response' then + self._previous_chunk = string.format('response<%s>', type(args_or_result)) if method_or_error == mpack.NIL then method_or_error = nil else