def decode_body(buffer, header=nil)
      return [nil, buffer] if buffer.size < Frame.header_size

      header ||= decode_header buffer[0, Frame.header_size + Frame.mac_size]
      body_size = Frame.decode_body_size header

      if @frame_cipher
        body = @frame_cipher.decrypt_body(buffer[(Frame.header_size+Frame.mac_size)..-1], body_size)
        raise MultiplexerError, 'body length mismatch' unless body.size == body_size

        bytes_read = Frame.header_size + Frame.mac_size + Utils.ceil16(body.size) + Frame.mac_size
      else
        header = buffer[0, Frame.header_size]
        body_offset = Frame.header_size + Frame.mac_size
        body = buffer[body_offset, body_size]
        raise MultiplexerError, 'body length mismatch' unless body.size == body_size

        bytes_read = Utils.ceil16(body_offset + body_size + Frame.mac_size)
      end
      raise MultiplexerError, "bytes not padded" unless bytes_read % Frame.padding == 0

      # normal, chunked-n: RLP::List.new(protocol_type[, sequence_id])
      # chunked-0: RLP::List.new(protocol_type, sequence_id, total_packet_size)
      header_data = nil
      begin
        header_data = RLP.decode(header[3..-1], sedes: Frame.header_sedes, strict: false)
      rescue RLP::Error::RLPException => e
        logger.error(e)
        raise MultiplexerError, 'invalid rlp data'
      end

      if header_data.size == 3
        chunked_0 = true
        total_payload_size = header_data[2]
        raise MultiplexerError, "invalid total payload size" unless total_payload_size < 2**32
      else
        chunked_0 = false
        total_payload_size = nil
      end

      protocol_id = header_data[0]
      raise MultiplexerError, "invalid protocol id" unless protocol_id < TT16

      if header_data.size > 1
        sequence_id = header_data[1]
        raise MultiplexerError, "invalid sequence id" unless sequence_id < TT16
      else
        sequence_id = nil
      end

      raise MultiplexerError, "unknown protocol id #{protocol_id}" unless @chunked_buffers.has_key?(protocol_id)

      chunkbuf = @chunked_buffers[protocol_id]
      if chunkbuf.has_key?(sequence_id)
        packet = chunkbuf[sequence_id]

        raise MultiplexerError, "received chunked_0 frame for existing buffer #{sequence_id} of protocol #{protocol_id}" if chunked_0
        raise MultiplexerError, "too much data for chunked buffer #{sequence_id} of protocol #{protocol_id}" if body.size > (packet.total_payload_size - packet.payload.size)

        packet.payload += body
        if packet.total_payload_size == packet.payload.size
          packet.total_payload_size = nil
          chunkbuf.delete sequence_id
          return packet
        end
      else
        # body of normal, chunked_0: rlp(packet-type) [|| rlp(packet-data)] || padding
        item, item_end = RLP.consume_item(body, 0)
        cmd_id = RLP::Sedes.big_endian_int.deserialize item

        if chunked_0
          payload = body[item_end..-1]
          total_payload_size -= item_end
        else
          payload = body[item_end..-1]
        end

        packet = Packet.new protocol_id, cmd_id, payload
        if chunked_0
          raise MultiplexerError, "total payload size smaller than initial chunk" if total_payload_size < payload.size

          # shouldn't have been chunked, whatever
          return packet if total_payload_size == payload.size

          raise MultiplexerError, 'chunked_0 must have sequence id' if sequence_id.nil?

          packet.total_payload_size = total_payload_size
          chunkbuf[sequence_id] = packet

          return nil
        else
          return packet # normal (non-chunked)
        end
      end
    end
def decode_body(buffer, header=nil)
      return [nil, buffer] if buffer.size < Frame.header_size

      header ||= decode_header buffer[0, Frame.header_size + Frame.mac_size]
      body_size = Frame.decode_body_size header

      if @frame_cipher
        body = @frame_cipher.decrypt_body(buffer[(Frame.header_size+Frame.mac_size)..-1], body_size)
        raise MultiplexerError, 'body length mismatch' unless body.size == body_size

        bytes_read = Frame.header_size + Frame.mac_size + Utils.ceil16(body.size) + Frame.mac_size
      else
        header = buffer[0, Frame.header_size]
        body_offset = Frame.header_size + Frame.mac_size
        body = buffer[body_offset, body_size]
        raise MultiplexerError, 'body length mismatch' unless body.size == body_size

        bytes_read = Utils.ceil16(body_offset + body_size + Frame.mac_size)
      end
      raise MultiplexerError, "bytes not padded" unless bytes_read % Frame.padding == 0

      # normal, chunked-n: RLP::List.new(protocol_type[, sequence_id])
      # chunked-0: RLP::List.new(protocol_type, sequence_id, total_packet_size)
      header_data = nil
      begin
        header_data = RLP.decode(header[3..-1], sedes: Frame.header_sedes, strict: false)
      rescue RLP::Error::RLPException => e
        logger.error(e)
        raise MultiplexerError, 'invalid rlp data'
      end

      if header_data.size == 3
        chunked_0 = true
        total_payload_size = header_data[2]
        raise MultiplexerError, "invalid total payload size" unless total_payload_size < 2**32
      else
        chunked_0 = false
        total_payload_size = nil
      end

      protocol_id = header_data[0]
      raise MultiplexerError, "invalid protocol id" unless protocol_id < TT16

      if header_data.size > 1
        sequence_id = header_data[1]
        raise MultiplexerError, "invalid sequence id" unless sequence_id < TT16
      else
        sequence_id = nil
      end

      raise MultiplexerError, "unknown protocol id #{protocol_id}" unless @chunked_buffers.has_key?(protocol_id)

      chunkbuf = @chunked_buffers[protocol_id]
      if chunkbuf.has_key?(sequence_id)
        packet = chunkbuf[sequence_id]

        raise MultiplexerError, "received chunked_0 frame for existing buffer #{sequence_id} of protocol #{protocol_id}" if chunked_0
        raise MultiplexerError, "too much data for chunked buffer #{sequence_id} of protocol #{protocol_id}" if body.size > (packet.total_payload_size - packet.payload.size)

        packet.payload += body
        if packet.total_payload_size == packet.payload.size
          packet.total_payload_size = nil
          chunkbuf.delete sequence_id
          return packet
        end
      else
        # body of normal, chunked_0: rlp(packet-type) [|| rlp(packet-data)] || padding
        item, item_end = RLP.consume_item(body, 0)
        cmd_id = RLP::Sedes.big_endian_int.deserialize item

        if chunked_0
          payload = body[item_end..-1]
          total_payload_size -= item_end
        else
          payload = body[item_end..-1]
        end

        packet = Packet.new protocol_id, cmd_id, payload
        if chunked_0
          raise MultiplexerError, "total payload size smaller than initial chunk" if total_payload_size < payload.size

          # shouldn't have been chunked, whatever
          return packet if total_payload_size == payload.size

          raise MultiplexerError, 'chunked_0 must have sequence id' if sequence_id.nil?

          packet.total_payload_size = total_payload_size
          chunkbuf[sequence_id] = packet

          return nil
        else
          return packet # normal (non-chunked)
        end
      end
    end
w/o encryption
 peak info buffer for body_size

 return nil if buffer is not long enough to decode frame