Skip to content

Commit 2031e43

Browse files
authored
ADR 30 : New handshake for the bolt protocol (#1243)
Implements the expanded handshake introduced in the modern bolt server, allowing for more granular selection of bolt protocol.
1 parent c993b99 commit 2031e43

File tree

8 files changed

+276
-8
lines changed

8 files changed

+276
-8
lines changed

packages/bolt-connection/src/bolt/handshake.js

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { alloc } from '../channel'
1919
import { newError } from 'neo4j-driver-core'
2020

2121
const BOLT_MAGIC_PREAMBLE = 0x6060b017
22+
const AVAILABLE_BOLT_PROTOCOLS = [5.8, 5.7, 5.6, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 3.0] // bolt protocols the client will accept, ordered by preference
23+
const DESIRED_CAPABILITES = 0
2224

2325
function version (major, minor) {
2426
return {
@@ -70,15 +72,69 @@ function parseNegotiatedResponse (buffer, log) {
7072
return Number(h[3] + '.' + h[2])
7173
}
7274

75+
function handshakeNegotiationV2 (channel, buffer, log) {
76+
const numVersions = buffer.readVarInt()
77+
let versions = []
78+
for (let i = 0; i < numVersions; i++) {
79+
const versionRange = [
80+
buffer.readUInt8(),
81+
buffer.readUInt8(),
82+
buffer.readUInt8(),
83+
buffer.readUInt8()
84+
]
85+
versions = versions.concat(getVersions(versionRange))
86+
}
87+
const capabilityBitMask = buffer.readVarInt()
88+
const capabilites = selectCapabilites(capabilityBitMask)
89+
90+
let major = 0
91+
let minor = 0
92+
versions.sort((a, b) => {
93+
if (Number(a.major) !== Number(b.major)) {
94+
return Number(b.major) - Number(a.major)
95+
} else {
96+
return Number(b.minor) - Number(a.minor)
97+
}
98+
})
99+
for (let i = 0; i < versions.length; i++) {
100+
const version = versions[i]
101+
if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) {
102+
major = version.major
103+
minor = version.minor
104+
break
105+
}
106+
}
107+
108+
return new Promise((resolve, reject) => {
109+
try {
110+
const selectionBuffer = alloc(5)
111+
selectionBuffer.writeInt32((minor << 8) | major)
112+
selectionBuffer.writeVarInt(capabilites)
113+
channel.write(selectionBuffer)
114+
resolve({
115+
protocolVersion: Number(major + '.' + minor),
116+
capabilites,
117+
consumeRemainingBuffer: consumer => {
118+
if (buffer.hasRemaining()) {
119+
consumer(buffer.readSlice(buffer.remaining()))
120+
}
121+
}
122+
})
123+
} catch (e) {
124+
reject(e)
125+
}
126+
})
127+
}
128+
73129
/**
74130
* @return {BaseBuffer}
75131
* @private
76132
*/
77133
function newHandshakeBuffer () {
78134
return createHandshakeMessage([
135+
version(255, 1),
79136
[version(5, 8), version(5, 0)],
80137
[version(4, 4), version(4, 2)],
81-
version(4, 1),
82138
version(3, 0)
83139
])
84140
}
@@ -91,8 +147,10 @@ function newHandshakeBuffer () {
91147
/**
92148
* @typedef HandshakeResult
93149
* @property {number} protocolVersion The protocol version negotiated in the handshake
150+
* @property {number} capabilites A bitmask representing the capabilities negotiated in the handshake
94151
* @property {function(BufferConsumerCallback)} consumeRemainingBuffer A function to consume the remaining buffer if it exists
95152
*/
153+
96154
/**
97155
* Shake hands using the channel and return the protocol version
98156
*
@@ -101,6 +159,23 @@ function newHandshakeBuffer () {
101159
* @returns {Promise<HandshakeResult>} Promise of protocol version and consumeRemainingBuffer
102160
*/
103161
export default function handshake (channel, log) {
162+
return initialHandshake(channel, log).then((result) => {
163+
if (result.protocolVersion === 255.1) {
164+
return handshakeNegotiationV2(channel, result.buffer, log)
165+
} else {
166+
return result
167+
}
168+
})
169+
}
170+
171+
/**
172+
* Shake hands using the channel and return the protocol version, or the improved handshake protocol if communicating with a newer server.
173+
*
174+
* @param {Channel} channel the channel use to shake hands
175+
* @param {Logger} log the log object
176+
* @returns {Promise<HandshakeResult>} Promise of protocol version and consumeRemainingBuffer
177+
*/
178+
function initialHandshake (channel, log) {
104179
return new Promise((resolve, reject) => {
105180
const handshakeErrorHandler = error => {
106181
reject(error)
@@ -115,9 +190,10 @@ export default function handshake (channel, log) {
115190
try {
116191
// read the response buffer and initialize the protocol
117192
const protocolVersion = parseNegotiatedResponse(buffer, log)
118-
119193
resolve({
120194
protocolVersion,
195+
capabilites: 0,
196+
buffer,
121197
consumeRemainingBuffer: consumer => {
122198
if (buffer.hasRemaining()) {
123199
consumer(buffer.readSlice(buffer.remaining()))
@@ -132,3 +208,17 @@ export default function handshake (channel, log) {
132208
channel.write(newHandshakeBuffer())
133209
})
134210
}
211+
212+
function getVersions (versionArray) {
213+
const resultArr = []
214+
const major = versionArray[3]
215+
const minor = versionArray[2]
216+
for (let i = 0; i <= versionArray[1]; i++) {
217+
resultArr.push({ major, minor: minor - i })
218+
}
219+
return resultArr
220+
}
221+
222+
function selectCapabilites (capabilityBitMask) {
223+
return DESIRED_CAPABILITES // capabilites are currently unused and will always be 0.
224+
}

packages/bolt-connection/src/buf/base-buf.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ export default class BaseBuffer {
4747
throw new Error('Not implemented')
4848
}
4949

50+
getVarInt (position) {
51+
throw new Error('Not implemented')
52+
}
53+
5054
putUInt8 (position, val) {
5155
throw new Error('Not implemented')
5256
}
@@ -178,6 +182,20 @@ export default class BaseBuffer {
178182
this.putUInt8(p + 7, val & 0xff)
179183
}
180184

185+
putVarInt (p, val) {
186+
let length = 0
187+
while (val > 1) {
188+
let int = val % 128
189+
if (val >= 128) {
190+
int += 128
191+
}
192+
val = val / 128
193+
this.putUInt8(p + length, int)
194+
length += 1
195+
}
196+
return length
197+
}
198+
181199
/**
182200
* @param position
183201
* @param other
@@ -244,6 +262,15 @@ export default class BaseBuffer {
244262
return this.getFloat64(this._updatePos(8))
245263
}
246264

265+
/**
266+
* Read from state position
267+
*/
268+
readVarInt () {
269+
const int = this.getVarInt(this.position)
270+
this._updatePos(int.length)
271+
return int.value
272+
}
273+
247274
/**
248275
* Write to state position.
249276
* @param val
@@ -300,6 +327,11 @@ export default class BaseBuffer {
300327
this.putFloat64(this._updatePos(8), val)
301328
}
302329

330+
writeVarInt (val) {
331+
const length = this.putVarInt(this.position, val)
332+
this._updatePos(length)
333+
}
334+
303335
/**
304336
* Write to state position.
305337
* @param val

packages/bolt-connection/src/channel/channel-buf.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ export default class ChannelBuffer extends BaseBuffer {
3737
return this._buffer.readDoubleBE(position)
3838
}
3939

40+
getVarInt (position) {
41+
let i = 0
42+
let currentValue = this._buffer.readInt8(position + i)
43+
let total = currentValue % 128
44+
while (currentValue / 128 >= 1) {
45+
i += 1
46+
currentValue = this._buffer.readInt8(position + i)
47+
total += currentValue % 128
48+
}
49+
return { length: i + 1, value: total }
50+
}
51+
4052
putUInt8 (position, val) {
4153
this._buffer.writeUInt8(val, position)
4254
}

packages/bolt-connection/test/bolt/index.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ describe('#unit Bolt', () => {
4848
const writtenBuffer = channel.written[0]
4949

5050
const boltMagicPreamble = '60 60 b0 17'
51-
const protocolVersion5x7to5x0 = '00 08 08 05'
51+
const handshakev2 = '00 00 01 ff'
52+
const protocolVersion5x8to5x0 = '00 08 08 05'
5253
const protocolVersion4x4to4x2 = '00 02 04 04'
53-
const protocolVersion4x1 = '00 00 01 04'
5454
const protocolVersion3 = '00 00 00 03'
5555

5656
expect(writtenBuffer.toHex()).toEqual(
57-
`${boltMagicPreamble} ${protocolVersion5x7to5x0} ${protocolVersion4x4to4x2} ${protocolVersion4x1} ${protocolVersion3}`
57+
`${boltMagicPreamble} ${handshakev2} ${protocolVersion5x8to5x0} ${protocolVersion4x4to4x2} ${protocolVersion3}`
5858
)
5959
})
6060

packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js

Lines changed: 92 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)