Class: Neo4j::Driver::Bolt::Handshake

Inherits:
Object
  • Object
show all
Defined in:
lib/neo4j/driver/bolt/handshake.rb

Overview

Bolt handshake negotiation. Two paths share the same 20-byte preamble (magic + four 4-byte slots, slot 1 = manifest sentinel):

Legacy: server replies with one of the proposed versions
      (4 bytes, big-endian). Pre-5.7 servers always take
      this path because they don't speak manifest.

Manifest v1: server replies with the sentinel 0x000001FF,
      then sends a varint count + N×4-byte version ranges +
      a varlong capabilities mask. Client picks the highest
      version it supports and writes back the chosen
      version (4 bytes) + a varlong of capabilities
      (currently always 0 — we negotiate no capabilities).

Mirrors BoltProtocolUtil / HandshakeHandler / ManifestHandlerV1 in neo4j-bolt-connection-java.

Constant Summary collapse

MAGIC_PREAMBLE =
"\x60\x60\xB0\x17".b
HTTP_REPLY =

First four bytes of an HTTP response ("HTTP") read as a big-endian int — a server speaking HTTP on the Bolt port.

0x4854_5450
MANIFEST_SENTINEL =

Slot 1 advertises "I speak HandshakeManifestV1". Decodes as BoltVersion(major=255, minor=1); servers that don't understand the sentinel ignore it and pick from slots 2-4 instead.

0x00_00_01_FF
SLOTS =

Range encoding: [reserved=0, range=N, minor, major]. Each slot covers major.minor down to major.(minor-N). We advertise 5.0–5.8 (slot 2), 4.2–4.4 (slot 3) and 3.0 (slot 4). Bolt 3.0 is fully wired now (Protocol::V3: PULL_ALL / DISCARD_ALL, single-DB, no routing in HELLO).

[
  MANIFEST_SENTINEL,
  0x00_08_08_05, # 5.0–5.8
  0x00_02_04_04, # 4.2–4.4
  0x00_00_00_03 # 3.0
].freeze
SUPPORTED_VERSIONS =

Versions we'd accept from a manifest, highest-preference first. Used to pick a winner from whatever the server advertises.

[
  BoltVersion::V6_1,
  BoltVersion::V6_0,
  BoltVersion::V5_8,
  BoltVersion::V5_7,
  BoltVersion::V5_6,
  BoltVersion::V5_5,
  BoltVersion::V5_4,
  BoltVersion::V5_3,
  BoltVersion::V5_2,
  BoltVersion::V5_1,
  BoltVersion::V5_0,
  BoltVersion::V4_4,
  BoltVersion::V4_3,
  BoltVersion::V4_2,
  BoltVersion::V3_0
].freeze
MAX_MANIFEST_RANGES =

Run the handshake to completion. Returns the negotiated version as a 32-bit int in the wire's slot encoding ([reserved, range=0, minor, major]). Sanity cap on the manifest's range-count varint. The varint itself is bounded to 63 usable bits but a server (malicious or buggy) could still ask us to allocate billions of slots. 256 is comfortably above the realistic upper bound (Java currently advertises ~5 ranges) and well below anything that'd hurt to allocate.

256

Instance Method Summary collapse

Constructor Details

#initialize(socket, deadline: nil, clock: Internal::Clock.new) ⇒ Handshake

deadline (monotonic) bounds the negotiation reads so a server that stalls the magic-byte exchange can't outlast the acquisition timeout. We read via wait_readable + read_nonblock rather than read(n)+IO#timeout because IO#timeout is honored for read() only on CRuby, not JRuby — wait_readable times out on both, so the bound works on every flavor.



71
72
73
74
75
# File 'lib/neo4j/driver/bolt/handshake.rb', line 71

def initialize(socket, deadline: nil, clock: Internal::Clock.new)
  @socket = socket
  @deadline = deadline
  @clock = clock
end

Instance Method Details

#negotiateObject



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/neo4j/driver/bolt/handshake.rb', line 88

def negotiate
  @socket.write(MAGIC_PREAMBLE)
  SLOTS.each { |slot| @socket.write([slot].pack('L>')) }
  @socket.flush

  server_reply = read_int32!('Server closed the connection during handshake')
  if server_reply.zero?
    raise Exceptions::ServiceUnavailableException,
          'Server does not support any of the proposed Bolt versions'
  end

  # An HTTP server on the Bolt port replies with an HTTP status
  # line; its first four bytes spell "HTTP". Mirror Java's helpful
  # error instead of reporting a bogus protocol version.
  if server_reply == HTTP_REPLY
    raise Exceptions::ClientException,
          'Server responded HTTP. Make sure you are not trying to connect to the http ' \
          'endpoint (HTTP defaults to port 7474 whereas BOLT defaults to port 7687)'
  end

  server_reply == MANIFEST_SENTINEL ? negotiate_manifest : server_reply
end