handshake.js•7.59 kB
"use strict";
/**
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var channel_1 = require("../channel");
var neo4j_driver_core_1 = require("neo4j-driver-core");
var BOLT_MAGIC_PREAMBLE = 0x6060b017;
var 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
var DESIRED_CAPABILITES = 0;
function version(major, minor) {
return {
major: major,
minor: minor
};
}
function createHandshakeMessage(versions) {
if (versions.length > 4) {
throw (0, neo4j_driver_core_1.newError)('It should not have more than 4 versions of the protocol');
}
var handshakeBuffer = (0, channel_1.alloc)(5 * 4);
handshakeBuffer.writeInt32(BOLT_MAGIC_PREAMBLE);
versions.forEach(function (version) {
if (version instanceof Array) {
var _a = version[0], major = _a.major, minor = _a.minor;
var minMinor = version[1].minor;
var range = minor - minMinor;
handshakeBuffer.writeInt32((range << 16) | (minor << 8) | major);
}
else {
var major = version.major, minor = version.minor;
handshakeBuffer.writeInt32((minor << 8) | major);
}
});
handshakeBuffer.reset();
return handshakeBuffer;
}
function parseNegotiatedResponse(buffer, log) {
var h = [
buffer.readUInt8(),
buffer.readUInt8(),
buffer.readUInt8(),
buffer.readUInt8()
];
if (h[0] === 0x48 && h[1] === 0x54 && h[2] === 0x54 && h[3] === 0x50) {
log.error('Handshake failed since server responded with HTTP.');
throw (0, neo4j_driver_core_1.newError)('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)');
}
return Number(h[3] + '.' + h[2]);
}
function handshakeNegotiationV2(channel, buffer, log) {
var numVersions = buffer.readVarInt();
var versions = [];
for (var i = 0; i < numVersions; i++) {
var versionRange = [
buffer.readUInt8(),
buffer.readUInt8(),
buffer.readUInt8(),
buffer.readUInt8()
];
versions = versions.concat(getVersions(versionRange));
}
var capabilityBitMask = buffer.readVarInt();
var capabilites = selectCapabilites(capabilityBitMask);
var major = 0;
var minor = 0;
versions.sort(function (a, b) {
if (Number(a.major) !== Number(b.major)) {
return Number(b.major) - Number(a.major);
}
else {
return Number(b.minor) - Number(a.minor);
}
});
for (var i = 0; i < versions.length; i++) {
var version_1 = versions[i];
if (AVAILABLE_BOLT_PROTOCOLS.includes(version_1.major + '.' + version_1.minor)) {
major = version_1.major;
minor = version_1.minor;
break;
}
}
return new Promise(function (resolve, reject) {
try {
var selectionBuffer = (0, channel_1.alloc)(5);
selectionBuffer.writeInt32((minor << 8) | major);
selectionBuffer.writeVarInt(capabilites);
channel.write(selectionBuffer);
resolve({
protocolVersion: Number(major + '.' + minor),
capabilites: capabilites,
consumeRemainingBuffer: function (consumer) {
if (buffer.hasRemaining()) {
consumer(buffer.readSlice(buffer.remaining()));
}
}
});
}
catch (e) {
reject(e);
}
});
}
/**
* @return {BaseBuffer}
* @private
*/
function newHandshakeBuffer() {
return createHandshakeMessage([
version(255, 1),
[version(5, 8), version(5, 0)],
[version(4, 4), version(4, 2)],
version(3, 0)
]);
}
/**
* This callback is displayed as a global member.
* @callback BufferConsumerCallback
* @param {buffer} buffer the remaining buffer
*/
/**
* @typedef HandshakeResult
* @property {number} protocolVersion The protocol version negotiated in the handshake
* @property {number} capabilites A bitmask representing the capabilities negotiated in the handshake
* @property {function(BufferConsumerCallback)} consumeRemainingBuffer A function to consume the remaining buffer if it exists
*/
/**
* Shake hands using the channel and return the protocol version
*
* @param {Channel} channel the channel use to shake hands
* @param {Logger} log the log object
* @returns {Promise<HandshakeResult>} Promise of protocol version and consumeRemainingBuffer
*/
function handshake(channel, log) {
return initialHandshake(channel, log).then(function (result) {
if (result.protocolVersion === 255.1) {
return handshakeNegotiationV2(channel, result.buffer, log);
}
else {
return result;
}
});
}
exports.default = handshake;
/**
* Shake hands using the channel and return the protocol version, or the improved handshake protocol if communicating with a newer server.
*
* @param {Channel} channel the channel use to shake hands
* @param {Logger} log the log object
* @returns {Promise<HandshakeResult>} Promise of protocol version and consumeRemainingBuffer
*/
function initialHandshake(channel, log) {
var _this = this;
return new Promise(function (resolve, reject) {
var handshakeErrorHandler = function (error) {
reject(error);
};
channel.onerror = handshakeErrorHandler.bind(_this);
if (channel._error) {
handshakeErrorHandler(channel._error);
}
channel.onmessage = function (buffer) {
try {
// read the response buffer and initialize the protocol
var protocolVersion = parseNegotiatedResponse(buffer, log);
resolve({
protocolVersion: protocolVersion,
capabilites: 0,
buffer: buffer,
consumeRemainingBuffer: function (consumer) {
if (buffer.hasRemaining()) {
consumer(buffer.readSlice(buffer.remaining()));
}
}
});
}
catch (e) {
reject(e);
}
};
channel.write(newHandshakeBuffer());
});
}
function getVersions(versionArray) {
var resultArr = [];
var major = versionArray[3];
var minor = versionArray[2];
for (var i = 0; i <= versionArray[1]; i++) {
resultArr.push({ major: major, minor: minor - i });
}
return resultArr;
}
function selectCapabilites(capabilityBitMask) {
return DESIRED_CAPABILITES; // capabilites are currently unused and will always be 0.
}