From e17125a24c5311d0e815985972194b04c283af39 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:54:15 +0100 Subject: [PATCH 01/22] Initial work. some dirty work and dangling comments --- .../bolt-connection/src/bolt/handshake.js | 72 +++++++++++++++++-- packages/bolt-connection/src/buf/base-buf.js | 24 +++++++ .../src/channel/channel-buf.js | 12 ++++ .../lib/bolt-connection/bolt/handshake.js | 72 +++++++++++++++++-- .../lib/bolt-connection/buf/base-buf.js | 24 +++++++ .../bolt-connection/channel/channel-buf.js | 12 ++++ .../testkit-backend/src/feature/common.js | 1 + testkit/testkit.json | 2 +- 8 files changed, 208 insertions(+), 11 deletions(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index abf318025..cc5bf0379 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -70,16 +70,58 @@ function parseNegotiatedResponse (buffer, log) { return Number(h[3] + '.' + h[2]) } +function newNegotiation (channel, buffer, log) { + const numVersions = buffer.readVarInt() + let versions = [] + for (let i = 0; i < numVersions; i++) { + const h = [ + buffer.readUInt8(), + buffer.readUInt8(), + buffer.readUInt8(), + buffer.readUInt8() + ] + versions = versions.concat([h]) + } + buffer.readVarInt() + // parse supported capabilities + // select preferrable protocol and respond + const major = versions[0][3] + const minor = versions[0][2] + + return new Promise((resolve, reject) => { + try { + const selectionBuffer = alloc(5) + selectionBuffer.writeInt32((minor << 8) | major) + // select capabilities and respond + const capabilites = 0 + selectionBuffer.writeVarInt(capabilites) + channel.write(selectionBuffer).then(() => { + resolve({ + protocolVersion: Number(major + '.' + minor), + capabilites: 0, + consumeRemainingBuffer: consumer => { + if (buffer.hasRemaining()) { + consumer(buffer.readSlice(buffer.remaining())) + } + } + }) + }) + } catch (e) { + reject(e) + } + }) +} + /** * @return {BaseBuffer} * @private */ function newHandshakeBuffer () { return createHandshakeMessage([ - [version(5, 7), version(5, 0)], - [version(4, 4), version(4, 2)], - version(4, 1), - version(3, 0) + version(255, 1), + version(5, 7), + [version(5, 5), version(5, 0)], + [version(4, 4), version(4, 1)] ]) } @@ -91,8 +133,10 @@ function newHandshakeBuffer () { /** * @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 * @@ -101,6 +145,23 @@ function newHandshakeBuffer () { * @returns {Promise} Promise of protocol version and consumeRemainingBuffer */ export default function handshake (channel, log) { + return initialHandshake(channel, log).then((result) => { + if (result.protocolVersion === 255.1) { + return newNegotiation(channel, result.buffer, log) + } else { + return result + } + }) +} + +/** + * 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} Promise of protocol version and consumeRemainingBuffer + */ +function initialHandshake (channel, log) { return new Promise((resolve, reject) => { const handshakeErrorHandler = error => { reject(error) @@ -115,9 +176,10 @@ export default function handshake (channel, log) { try { // read the response buffer and initialize the protocol const protocolVersion = parseNegotiatedResponse(buffer, log) - resolve({ protocolVersion, + capabilites: 0, + buffer, consumeRemainingBuffer: consumer => { if (buffer.hasRemaining()) { consumer(buffer.readSlice(buffer.remaining())) diff --git a/packages/bolt-connection/src/buf/base-buf.js b/packages/bolt-connection/src/buf/base-buf.js index 93d5f231d..88dcf37da 100644 --- a/packages/bolt-connection/src/buf/base-buf.js +++ b/packages/bolt-connection/src/buf/base-buf.js @@ -47,6 +47,10 @@ export default class BaseBuffer { throw new Error('Not implemented') } + getVarInt (position) { + throw new Error('Not implemented') + } + putUInt8 (position, val) { throw new Error('Not implemented') } @@ -244,6 +248,15 @@ export default class BaseBuffer { return this.getFloat64(this._updatePos(8)) } + /** + * Read from state position + */ + readVarInt () { + const int = this.getVarInt(this.position) + this._updatePos(int.length) + return int.value + } + /** * Write to state position. * @param val @@ -300,6 +313,17 @@ export default class BaseBuffer { this.putFloat64(this._updatePos(8), val) } + writeVarInt (val) { + while (val > 1) { + let int = val % 128 + if (val >= 128) { + int += 128 + } + val = val / 128 + this.putInt8(this._updatePos(1), int) + } + } + /** * Write to state position. * @param val diff --git a/packages/bolt-connection/src/channel/channel-buf.js b/packages/bolt-connection/src/channel/channel-buf.js index 8fabb2114..a5763aa44 100644 --- a/packages/bolt-connection/src/channel/channel-buf.js +++ b/packages/bolt-connection/src/channel/channel-buf.js @@ -37,6 +37,18 @@ export default class ChannelBuffer extends BaseBuffer { return this._buffer.readDoubleBE(position) } + getVarInt (position) { + let i = 0 + let currentValue = this._buffer.readInt8(position + i) + let total = currentValue % 128 + while (currentValue / 128 >= 1) { + i += 1 + currentValue = this._buffer.readInt8(position + i) + total += currentValue % 128 + } + return { length: i + 1, value: total } + } + putUInt8 (position, val) { this._buffer.writeUInt8(val, position) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index c91e0e18f..2bd859b6a 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -70,16 +70,58 @@ function parseNegotiatedResponse (buffer, log) { return Number(h[3] + '.' + h[2]) } +function newNegotiation (channel, buffer, log) { + const numVersions = buffer.readVarInt() + let versions = [] + for (let i = 0; i < numVersions; i++) { + const h = [ + buffer.readUInt8(), + buffer.readUInt8(), + buffer.readUInt8(), + buffer.readUInt8() + ] + versions = versions.concat([h]) + } + buffer.readVarInt() + // parse supported capabilities + // select preferrable protocol and respond + const major = versions[0][3] + const minor = versions[0][2] + + return new Promise((resolve, reject) => { + try { + const selectionBuffer = alloc(5) + selectionBuffer.writeInt32((minor << 8) | major) + // select capabilities and respond + const capabilites = 0 + selectionBuffer.writeVarInt(capabilites) + channel.write(selectionBuffer).then(() => { + resolve({ + protocolVersion: Number(major + '.' + minor), + capabilites: 0, + consumeRemainingBuffer: consumer => { + if (buffer.hasRemaining()) { + consumer(buffer.readSlice(buffer.remaining())) + } + } + }) + }) + } catch (e) { + reject(e) + } + }) +} + /** * @return {BaseBuffer} * @private */ function newHandshakeBuffer () { return createHandshakeMessage([ - [version(5, 7), version(5, 0)], - [version(4, 4), version(4, 2)], - version(4, 1), - version(3, 0) + version(255, 1), + version(5, 7), + [version(5, 5), version(5, 0)], + [version(4, 4), version(4, 1)] ]) } @@ -91,8 +133,10 @@ function newHandshakeBuffer () { /** * @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 * @@ -101,6 +145,23 @@ function newHandshakeBuffer () { * @returns {Promise} Promise of protocol version and consumeRemainingBuffer */ export default function handshake (channel, log) { + return initialHandshake(channel, log).then((result) => { + if (result.protocolVersion === 255.1) { + return newNegotiation(channel, result.buffer, log) + } else { + return result + } + }) +} + +/** + * 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} Promise of protocol version and consumeRemainingBuffer + */ +function initialHandshake (channel, log) { return new Promise((resolve, reject) => { const handshakeErrorHandler = error => { reject(error) @@ -115,9 +176,10 @@ export default function handshake (channel, log) { try { // read the response buffer and initialize the protocol const protocolVersion = parseNegotiatedResponse(buffer, log) - resolve({ protocolVersion, + capabilites: 0, + buffer, consumeRemainingBuffer: consumer => { if (buffer.hasRemaining()) { consumer(buffer.readSlice(buffer.remaining())) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js b/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js index 93d5f231d..88dcf37da 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js @@ -47,6 +47,10 @@ export default class BaseBuffer { throw new Error('Not implemented') } + getVarInt (position) { + throw new Error('Not implemented') + } + putUInt8 (position, val) { throw new Error('Not implemented') } @@ -244,6 +248,15 @@ export default class BaseBuffer { return this.getFloat64(this._updatePos(8)) } + /** + * Read from state position + */ + readVarInt () { + const int = this.getVarInt(this.position) + this._updatePos(int.length) + return int.value + } + /** * Write to state position. * @param val @@ -300,6 +313,17 @@ export default class BaseBuffer { this.putFloat64(this._updatePos(8), val) } + writeVarInt (val) { + while (val > 1) { + let int = val % 128 + if (val >= 128) { + int += 128 + } + val = val / 128 + this.putInt8(this._updatePos(1), int) + } + } + /** * Write to state position. * @param val diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/channel/channel-buf.js b/packages/neo4j-driver-deno/lib/bolt-connection/channel/channel-buf.js index c433c59b3..c0674f5d7 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/channel/channel-buf.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/channel/channel-buf.js @@ -37,6 +37,18 @@ export default class ChannelBuffer extends BaseBuffer { return this._buffer.readDoubleBE(position) } + getVarInt (position) { + let i = 0 + let currentValue = this._buffer.readInt8(position + i) + let total = currentValue % 128 + while (currentValue / 128 >= 1) { + i += 1 + currentValue = this._buffer.readInt8(position + i) + total += currentValue % 128 + } + return { length: i + 1, value: total } + } + putUInt8 (position, val) { this._buffer.writeUInt8(val, position) } diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index 5dec96283..9b51f56d4 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -27,6 +27,7 @@ const features = [ 'Feature:Bolt:5.5', 'Feature:Bolt:5.6', 'Feature:Bolt:5.7', + 'Feature:Bolt:HandshakeManifestV1', 'Feature:Bolt:Patch:UTC', 'Feature:API:ConnectionAcquisitionTimeout', 'Feature:API:Driver.ExecuteQuery', diff --git a/testkit/testkit.json b/testkit/testkit.json index 931900356..9e496140c 100644 --- a/testkit/testkit.json +++ b/testkit/testkit.json @@ -1,6 +1,6 @@ { "testkit": { "uri": "https://github.com/neo4j-drivers/testkit.git", - "ref": "5.0" + "ref": "bolt-handshake-v2" } } From daf9f43f9ad51f48764919488be1e2a5ee003693 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:47:40 +0100 Subject: [PATCH 02/22] fix small issue --- packages/bolt-connection/src/bolt/handshake.js | 17 ++++++++--------- .../lib/bolt-connection/bolt/handshake.js | 17 ++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index cc5bf0379..d5656765e 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -95,16 +95,15 @@ function newNegotiation (channel, buffer, log) { // select capabilities and respond const capabilites = 0 selectionBuffer.writeVarInt(capabilites) - channel.write(selectionBuffer).then(() => { - resolve({ - protocolVersion: Number(major + '.' + minor), - capabilites: 0, - consumeRemainingBuffer: consumer => { - if (buffer.hasRemaining()) { - consumer(buffer.readSlice(buffer.remaining())) - } + channel.write(selectionBuffer) + resolve({ + protocolVersion: Number(major + '.' + minor), + capabilites: 0, + consumeRemainingBuffer: consumer => { + if (buffer.hasRemaining()) { + consumer(buffer.readSlice(buffer.remaining())) } - }) + } }) } catch (e) { reject(e) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index 2bd859b6a..a102471a2 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -95,16 +95,15 @@ function newNegotiation (channel, buffer, log) { // select capabilities and respond const capabilites = 0 selectionBuffer.writeVarInt(capabilites) - channel.write(selectionBuffer).then(() => { - resolve({ - protocolVersion: Number(major + '.' + minor), - capabilites: 0, - consumeRemainingBuffer: consumer => { - if (buffer.hasRemaining()) { - consumer(buffer.readSlice(buffer.remaining())) - } + channel.write(selectionBuffer) + resolve({ + protocolVersion: Number(major + '.' + minor), + capabilites: 0, + consumeRemainingBuffer: consumer => { + if (buffer.hasRemaining()) { + consumer(buffer.readSlice(buffer.remaining())) } - }) + } }) } catch (e) { reject(e) From 22b21d808b13f756e3712f92e520b3906d9f3b36 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:15:30 +0100 Subject: [PATCH 03/22] change versions in handshake --- packages/bolt-connection/src/bolt/handshake.js | 6 +++--- .../neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index d5656765e..e86dc2a50 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -118,9 +118,9 @@ function newNegotiation (channel, buffer, log) { function newHandshakeBuffer () { return createHandshakeMessage([ version(255, 1), - version(5, 7), - [version(5, 5), version(5, 0)], - [version(4, 4), version(4, 1)] + [version(5, 7), version(5, 0)], + [version(4, 4), version(4, 0)], + version(3, 0) ]) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index a102471a2..1f9f28d37 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -118,9 +118,9 @@ function newNegotiation (channel, buffer, log) { function newHandshakeBuffer () { return createHandshakeMessage([ version(255, 1), - version(5, 7), - [version(5, 5), version(5, 0)], - [version(4, 4), version(4, 1)] + [version(5, 7), version(5, 0)], + [version(4, 4), version(4, 0)], + version(3, 0) ]) } From a1c19c1ce525e1a6c60c388c1bfc8398db55e8aa Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:10:16 +0100 Subject: [PATCH 04/22] Update index.test.js --- packages/bolt-connection/test/bolt/index.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bolt-connection/test/bolt/index.test.js b/packages/bolt-connection/test/bolt/index.test.js index 1a9117556..e4990191e 100644 --- a/packages/bolt-connection/test/bolt/index.test.js +++ b/packages/bolt-connection/test/bolt/index.test.js @@ -48,13 +48,13 @@ describe('#unit Bolt', () => { const writtenBuffer = channel.written[0] const boltMagicPreamble = '60 60 b0 17' + const handshakev2 = '00 00 01 ff' const protocolVersion5x7to5x0 = '00 07 07 05' - const protocolVersion4x4to4x2 = '00 02 04 04' - const protocolVersion4x1 = '00 00 01 04' + const protocolVersion4x4to4x0 = '00 04 04 04' const protocolVersion3 = '00 00 00 03' expect(writtenBuffer.toHex()).toEqual( - `${boltMagicPreamble} ${protocolVersion5x7to5x0} ${protocolVersion4x4to4x2} ${protocolVersion4x1} ${protocolVersion3}` + `${boltMagicPreamble} ${handshakev2} ${protocolVersion5x7to5x0} ${protocolVersion4x4to4x0} ${protocolVersion3}` ) }) From 5034a5a7d86d47088b17d6344d9cea2841b0dd58 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:43:41 +0100 Subject: [PATCH 05/22] removed bolt 4.1 feature --- packages/testkit-backend/src/feature/common.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index 9b51f56d4..c7454b44a 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -15,7 +15,6 @@ const features = [ 'ConfHint:connection.recv_timeout_seconds', 'Feature:Impersonation', 'Feature:Bolt:3.0', - 'Feature:Bolt:4.1', 'Feature:Bolt:4.2', 'Feature:Bolt:4.3', 'Feature:Bolt:4.4', From 5a7e84270a14f2340d11886e25f36f26d98f5e61 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:39:03 +0100 Subject: [PATCH 06/22] version select ordering --- .../bolt-connection/src/bolt/handshake.js | 38 +++++++++++++++---- .../lib/bolt-connection/bolt/handshake.js | 38 +++++++++++++++---- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index e86dc2a50..4d3048f74 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -19,6 +19,8 @@ import { alloc } from '../channel' import { newError } from 'neo4j-driver-core' const BOLT_MAGIC_PREAMBLE = 0x6060b017 +const AVAILABLE_BOLT_PROTOCOLS = [5.7, 5.5, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 4.1, 4.0, 3.0] +const DESIRED_CAPABILITES = 0 function version (major, minor) { return { @@ -80,25 +82,33 @@ function newNegotiation (channel, buffer, log) { buffer.readUInt8(), buffer.readUInt8() ] - versions = versions.concat([h]) + versions = versions.concat(getVersions(h)) } - buffer.readVarInt() + const capabilityBitMask = buffer.readVarInt() + const capabilites = selectCapabilites(capabilityBitMask) + // parse supported capabilities // select preferrable protocol and respond - const major = versions[0][3] - const minor = versions[0][2] + let major + let minor + for (let i = 0; i < versions.length; i++) { + const version = versions[i] + if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) { + major = version.major + minor = version.minor + break + } + } return new Promise((resolve, reject) => { try { const selectionBuffer = alloc(5) selectionBuffer.writeInt32((minor << 8) | major) - // select capabilities and respond - const capabilites = 0 selectionBuffer.writeVarInt(capabilites) channel.write(selectionBuffer) resolve({ protocolVersion: Number(major + '.' + minor), - capabilites: 0, + capabilites, consumeRemainingBuffer: consumer => { if (buffer.hasRemaining()) { consumer(buffer.readSlice(buffer.remaining())) @@ -193,3 +203,17 @@ function initialHandshake (channel, log) { channel.write(newHandshakeBuffer()) }) } + +function getVersions (versionArray) { + const resultArr = [] + const major = versionArray[3] + const minor = versionArray[2] + for (let i = 0; i <= versionArray[1]; i++) { + resultArr.push({ major, minor: minor - i }) + } + return resultArr +} + +function selectCapabilites (capabilityBitMask) { + return DESIRED_CAPABILITES // capabilites are currently unused and will always be 0. +} diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index 1f9f28d37..7dd546eea 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -19,6 +19,8 @@ import { alloc } from '../channel/index.js' import { newError } from '../../core/index.ts' const BOLT_MAGIC_PREAMBLE = 0x6060b017 +const AVAILABLE_BOLT_PROTOCOLS = [5.7, 5.5, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 4.1, 4.0, 3.0] +const DESIRED_CAPABILITES = 0 function version (major, minor) { return { @@ -80,25 +82,33 @@ function newNegotiation (channel, buffer, log) { buffer.readUInt8(), buffer.readUInt8() ] - versions = versions.concat([h]) + versions = versions.concat(getVersions(h)) } - buffer.readVarInt() + const capabilityBitMask = buffer.readVarInt() + const capabilites = selectCapabilites(capabilityBitMask) + // parse supported capabilities // select preferrable protocol and respond - const major = versions[0][3] - const minor = versions[0][2] + let major + let minor + for (let i = 0; i < versions.length; i++) { + const version = versions[i] + if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) { + major = version.major + minor = version.minor + break + } + } return new Promise((resolve, reject) => { try { const selectionBuffer = alloc(5) selectionBuffer.writeInt32((minor << 8) | major) - // select capabilities and respond - const capabilites = 0 selectionBuffer.writeVarInt(capabilites) channel.write(selectionBuffer) resolve({ protocolVersion: Number(major + '.' + minor), - capabilites: 0, + capabilites, consumeRemainingBuffer: consumer => { if (buffer.hasRemaining()) { consumer(buffer.readSlice(buffer.remaining())) @@ -193,3 +203,17 @@ function initialHandshake (channel, log) { channel.write(newHandshakeBuffer()) }) } + +function getVersions (versionArray) { + const resultArr = [] + const major = versionArray[3] + const minor = versionArray[2] + for (let i = 0; i <= versionArray[1]; i++) { + resultArr.push({ major, minor: minor - i }) + } + return resultArr +} + +function selectCapabilites (capabilityBitMask) { + return DESIRED_CAPABILITES // capabilites are currently unused and will always be 0. +} From 5e9830018080108c5eada09301de8c058b441fc7 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:26:27 +0100 Subject: [PATCH 07/22] exclude bolt 5.5, include 5.6 --- packages/bolt-connection/src/bolt/handshake.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index 4d3048f74..3769eaf5e 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -19,7 +19,7 @@ import { alloc } from '../channel' import { newError } from 'neo4j-driver-core' const BOLT_MAGIC_PREAMBLE = 0x6060b017 -const AVAILABLE_BOLT_PROTOCOLS = [5.7, 5.5, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 4.1, 4.0, 3.0] +const AVAILABLE_BOLT_PROTOCOLS = [5.7, 5.6, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 4.1, 4.0, 3.0] // bolt protocols the client supports, ordered by preference const DESIRED_CAPABILITES = 0 function version (major, minor) { From 2cbd0cb94aee99e9d68b258f8d96f6e9b9abdcf6 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:48:17 +0100 Subject: [PATCH 08/22] deno --- .../neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index 7dd546eea..ea48f1b21 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -19,7 +19,7 @@ import { alloc } from '../channel/index.js' import { newError } from '../../core/index.ts' const BOLT_MAGIC_PREAMBLE = 0x6060b017 -const AVAILABLE_BOLT_PROTOCOLS = [5.7, 5.5, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 4.1, 4.0, 3.0] +const AVAILABLE_BOLT_PROTOCOLS = [5.7, 5.6, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 4.1, 4.0, 3.0] // bolt protocols the client supports, ordered by preference const DESIRED_CAPABILITES = 0 function version (major, minor) { From c6b842cc11bd35bb62e5efe194293985094c5905 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 9 Jan 2025 09:46:23 +0100 Subject: [PATCH 09/22] ensure the highest available version is selected --- packages/bolt-connection/src/bolt/handshake.js | 9 +++++---- .../lib/bolt-connection/bolt/handshake.js | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index 3769eaf5e..aba4429db 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -72,17 +72,17 @@ function parseNegotiatedResponse (buffer, log) { return Number(h[3] + '.' + h[2]) } -function newNegotiation (channel, buffer, log) { +function handshakeNegotiationV2 (channel, buffer, log) { const numVersions = buffer.readVarInt() let versions = [] for (let i = 0; i < numVersions; i++) { - const h = [ + const versionRange = [ buffer.readUInt8(), buffer.readUInt8(), buffer.readUInt8(), buffer.readUInt8() ] - versions = versions.concat(getVersions(h)) + versions = versions.concat(getVersions(versionRange)) } const capabilityBitMask = buffer.readVarInt() const capabilites = selectCapabilites(capabilityBitMask) @@ -91,6 +91,7 @@ function newNegotiation (channel, buffer, log) { // select preferrable protocol and respond let major let minor + versions.sort((a, b) => Number(a.major + '.' + a.minor) - Number(b.major + '.' + b.minor)) for (let i = 0; i < versions.length; i++) { const version = versions[i] if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) { @@ -156,7 +157,7 @@ function newHandshakeBuffer () { export default function handshake (channel, log) { return initialHandshake(channel, log).then((result) => { if (result.protocolVersion === 255.1) { - return newNegotiation(channel, result.buffer, log) + return handshakeNegotiationV2(channel, result.buffer, log) } else { return result } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index ea48f1b21..481f2c6fb 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -72,17 +72,17 @@ function parseNegotiatedResponse (buffer, log) { return Number(h[3] + '.' + h[2]) } -function newNegotiation (channel, buffer, log) { +function handshakeNegotiationV2 (channel, buffer, log) { const numVersions = buffer.readVarInt() let versions = [] for (let i = 0; i < numVersions; i++) { - const h = [ + const versionRange = [ buffer.readUInt8(), buffer.readUInt8(), buffer.readUInt8(), buffer.readUInt8() ] - versions = versions.concat(getVersions(h)) + versions = versions.concat(getVersions(versionRange)) } const capabilityBitMask = buffer.readVarInt() const capabilites = selectCapabilites(capabilityBitMask) @@ -91,6 +91,7 @@ function newNegotiation (channel, buffer, log) { // select preferrable protocol and respond let major let minor + versions.sort((a, b) => Number(a.major + '.' + a.minor) - Number(b.major + '.' + b.minor)) for (let i = 0; i < versions.length; i++) { const version = versions[i] if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) { @@ -156,7 +157,7 @@ function newHandshakeBuffer () { export default function handshake (channel, log) { return initialHandshake(channel, log).then((result) => { if (result.protocolVersion === 255.1) { - return newNegotiation(channel, result.buffer, log) + return handshakeNegotiationV2(channel, result.buffer, log) } else { return result } From 3104a297238a4d6a9f78e4722f9118190390c4c6 Mon Sep 17 00:00:00 2001 From: Max Gustafsson <61233757+MaxAake@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:16:30 +0100 Subject: [PATCH 10/22] fix documentation of supportsUserImpersonation (#1241) --- packages/core/src/connection-provider.ts | 2 +- packages/neo4j-driver-deno/lib/core/connection-provider.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index 0cfacc1d4..e65795b41 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -91,7 +91,7 @@ class ConnectionProvider { } /** - * This method checks whether the backend database supports transaction config functionality + * This method checks whether the backend database supports user impersonation functionality * by checking protocol handshake result. * * @returns {Promise} diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index 977aeeada..ebc4803bf 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -91,7 +91,7 @@ class ConnectionProvider { } /** - * This method checks whether the backend database supports transaction config functionality + * This method checks whether the backend database supports user impersonation functionality * by checking protocol handshake result. * * @returns {Promise} From ff3dd5f8fc1356a5136243cff5283aa60eff93f1 Mon Sep 17 00:00:00 2001 From: Max Gustafsson <61233757+MaxAake@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:32:29 +0100 Subject: [PATCH 11/22] fixing minute offset issue in certain time zones (#1238) Timezones with minute offsets not divisible by 60 were causing issues for converting from the driver implementation of Date and Datetime to JS Date. A better implementation has been created and some documentation has been updated to reflect limitations that already existed. --- packages/bolt-connection/test/test-utils.js | 7 -- packages/core/src/internal/temporal-util.ts | 56 ++++++++++--- packages/core/src/temporal-types.ts | 6 ++ packages/core/test/temporal-types.test.ts | 84 ++++++++----------- .../lib/core/internal/temporal-util.ts | 56 ++++++++++--- .../lib/core/temporal-types.ts | 6 ++ .../test/internal/temporal-util.test.js | 22 ----- .../neo4j-driver/test/internal/test-utils.js | 7 -- .../neo4j-driver/test/temporal-types.test.js | 57 ------------- 9 files changed, 133 insertions(+), 168 deletions(-) diff --git a/packages/bolt-connection/test/test-utils.js b/packages/bolt-connection/test/test-utils.js index 9fd6c03a8..c0d225199 100644 --- a/packages/bolt-connection/test/test-utils.js +++ b/packages/bolt-connection/test/test-utils.js @@ -31,12 +31,6 @@ function isServer () { return !isClient() } -function fakeStandardDateWithOffset (offsetMinutes) { - const date = new Date() - date.getTimezoneOffset = () => offsetMinutes - return date -} - const matchers = { toBeElementOf: function (actual, expected) { if (expected === undefined) { @@ -161,7 +155,6 @@ function arbitraryTimeZoneId () { export default { isClient, isServer, - fakeStandardDateWithOffset, matchers, MessageRecordingConnection, spyProtocolWrite, diff --git a/packages/core/src/internal/temporal-util.ts b/packages/core/src/internal/temporal-util.ts index 2951d5ac5..ece38c424 100644 --- a/packages/core/src/internal/temporal-util.ts +++ b/packages/core/src/internal/temporal-util.ts @@ -339,24 +339,54 @@ export function totalNanoseconds ( /** * Get the time zone offset in seconds from the given standard JavaScript date. * - * Implementation note: - * Time zone offset returned by the standard JavaScript date is the difference, in minutes, from local time to UTC. - * So positive value means offset is behind UTC and negative value means it is ahead. - * For Neo4j temporal types, like `Time` or `DateTime` offset is in seconds and represents difference from UTC to local time. - * This is different from standard JavaScript dates and that's why implementation negates the returned value. - * * @param {global.Date} standardDate the standard JavaScript date. * @return {number} the time zone offset in seconds. */ export function timeZoneOffsetInSeconds (standardDate: Date): number { - const secondsPortion = standardDate.getSeconds() >= standardDate.getUTCSeconds() - ? standardDate.getSeconds() - standardDate.getUTCSeconds() - : standardDate.getSeconds() - standardDate.getUTCSeconds() + 60 - const offsetInMinutes = standardDate.getTimezoneOffset() - if (offsetInMinutes === 0) { - return 0 + secondsPortion + const secondsPortion = standardDate.getSeconds() - standardDate.getUTCSeconds() + const minutesPortion = standardDate.getMinutes() - standardDate.getUTCMinutes() + const hoursPortion = standardDate.getHours() - standardDate.getUTCHours() + const daysPortion = _getDayOffset(standardDate) + return hoursPortion * SECONDS_PER_HOUR + minutesPortion * SECONDS_PER_MINUTE + secondsPortion + daysPortion * SECONDS_PER_DAY +} + +/** + * Get the difference in days from the given JavaScript date in local time and UTC. + * + * @private + * @param {global.Date} standardDate the date to evaluate + * @returns {number} the difference in days between date local time and UTC + */ +function _getDayOffset (standardDate: Date): number { + if (standardDate.getMonth() === standardDate.getUTCMonth()) { + return standardDate.getDate() - standardDate.getUTCDate() + } else if ((standardDate.getFullYear() > standardDate.getUTCFullYear()) || (standardDate.getMonth() > standardDate.getUTCMonth() && standardDate.getFullYear() === standardDate.getUTCFullYear())) { + return standardDate.getDate() + _daysUntilNextMonth(standardDate.getUTCMonth(), standardDate.getUTCFullYear()) - standardDate.getUTCDate() + } else { + return standardDate.getDate() - (standardDate.getUTCDate() + _daysUntilNextMonth(standardDate.getMonth(), standardDate.getFullYear())) + } +} + +/** + * Get the number of days in a month, including a check for leap years. + * + * @private + * @param {number} month the month of the date to evalutate + * @param {number} year the month of the date to evalutate + * @returns {number} the total number of days in the month evaluated + */ +function _daysUntilNextMonth (month: number, year: number): number { + if (month === 1) { + if (year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)) { + return 29 + } else { + return 28 + } + } else if ([0, 2, 4, 6, 7, 9, 11].includes(month)) { + return 31 + } else { + return 30 } - return -1 * offsetInMinutes * SECONDS_PER_MINUTE + secondsPortion } /** diff --git a/packages/core/src/temporal-types.ts b/packages/core/src/temporal-types.ts index a9837d2d0..7ea533506 100644 --- a/packages/core/src/temporal-types.ts +++ b/packages/core/src/temporal-types.ts @@ -353,6 +353,10 @@ export class Date { /** * Create a {@link Date} object from the given standard JavaScript `Date`. * Hour, minute, second, millisecond and time zone offset components of the given date are ignored. + * + * NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0 + * If your timezone has a negative offset from UTC, creating a JavaScript Date at midnight UTC and converting it with {@link fromStandardDate} will result in a Date for the day before. + * * @param {global.Date} standardDate - The standard JavaScript date to convert. * @return {Date} New Date. */ @@ -372,6 +376,8 @@ export class Date { * The time component of the returned `Date` is set to midnight * and the time zone is set to UTC. * + * NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0 + * * @returns {StandardDate} Standard JavaScript `Date` at `00:00:00.000` UTC. */ toStandardDate (): StandardDate { diff --git a/packages/core/test/temporal-types.test.ts b/packages/core/test/temporal-types.test.ts index d51d0057d..4341be584 100644 --- a/packages/core/test/temporal-types.test.ts +++ b/packages/core/test/temporal-types.test.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { StandardDate } from '../src/graph-types' import { LocalDateTime, Date, DateTime, Duration, isDuration, LocalTime, isLocalTime, Time, isTime, isDate, isLocalDateTime, isDateTime } from '../src/temporal-types' import { temporalUtil } from '../src/internal' import fc from 'fast-check' @@ -31,9 +30,9 @@ describe('Date', () => { const standardDate = localDatetime.toStandardDate() - expect(standardDate.getFullYear()).toEqual(localDatetime.year) - expect(standardDate.getMonth()).toEqual(localDatetime.month - 1) - expect(standardDate.getDate()).toEqual(localDatetime.day) + expect(standardDate.getUTCFullYear()).toEqual(localDatetime.year) + expect(standardDate.getUTCMonth()).toEqual(localDatetime.month - 1) + expect(standardDate.getUTCDate()).toEqual(localDatetime.day) }) it('should be the reverse operation of fromStandardDate but losing time information', () => { @@ -47,14 +46,11 @@ describe('Date', () => { const date = Date.fromStandardDate(standardDate) const receivedDate = date.toStandardDate() - const adjustedDateTime = temporalUtil.newDate(standardDate) - adjustedDateTime.setHours(0, offset(receivedDate)) - - expect(receivedDate.getFullYear()).toEqual(adjustedDateTime.getFullYear()) - expect(receivedDate.getMonth()).toEqual(adjustedDateTime.getMonth()) - expect(receivedDate.getDate()).toEqual(adjustedDateTime.getDate()) - expect(receivedDate.getHours()).toEqual(adjustedDateTime.getHours()) - expect(receivedDate.getMinutes()).toEqual(adjustedDateTime.getMinutes()) + expect(receivedDate.getUTCFullYear()).toEqual(standardDate.getFullYear()) // Date converts from local time but to UTC + expect(receivedDate.getUTCMonth()).toEqual(standardDate.getMonth()) + expect(receivedDate.getUTCDate()).toEqual(standardDate.getDate()) + expect(receivedDate.getUTCHours()).toEqual(0) + expect(receivedDate.getUTCMinutes()).toEqual(0) }) ) }) @@ -113,17 +109,16 @@ describe('DateTime', () => { const standardDate = datetime.toStandardDate() - expect(standardDate.getFullYear()).toEqual(datetime.year) - expect(standardDate.getMonth()).toEqual(datetime.month - 1) - expect(standardDate.getDate()).toEqual(datetime.day) - const offsetInMinutes = offset(standardDate) - const offsetAdjust = offsetInMinutes - (datetime.timeZoneOffsetSeconds ?? 0) / 60 - const hourDiff = Math.abs(offsetAdjust / 60) + expect(standardDate.getUTCFullYear()).toEqual(datetime.year) + expect(standardDate.getUTCMonth()).toEqual(datetime.month - 1) + expect(standardDate.getUTCDate()).toEqual(datetime.day) // The datetime in this test will never cross the date line in conversion, it is therefore safe to use UTC here to avoid machine timezone from altering the result of the test. + const offsetAdjust = (datetime.timeZoneOffsetSeconds ?? 0) / 60 + const hourDiff = Math.abs((offsetAdjust - offsetAdjust % 60) / 60) const minuteDiff = Math.abs(offsetAdjust % 60) - expect(standardDate.getHours()).toBe(datetime.hour - hourDiff) - expect(standardDate.getMinutes()).toBe(datetime.minute - minuteDiff) - expect(standardDate.getSeconds()).toBe(datetime.second) - expect(standardDate.getMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000)) + expect(standardDate.getUTCHours()).toBe(datetime.hour - hourDiff) + expect(standardDate.getUTCMinutes()).toBe(datetime.minute - minuteDiff) + expect(standardDate.getUTCSeconds()).toBe(datetime.second) + expect(standardDate.getUTCMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000)) }) it('should convert to a standard date (offset)', () => { @@ -131,17 +126,16 @@ describe('DateTime', () => { const standardDate = datetime.toStandardDate() - expect(standardDate.getFullYear()).toEqual(datetime.year) - expect(standardDate.getMonth()).toEqual(datetime.month - 1) - expect(standardDate.getDate()).toEqual(datetime.day) - const offsetInMinutes = offset(standardDate) - const offsetAdjust = offsetInMinutes - (datetime.timeZoneOffsetSeconds ?? 0) / 60 - const hourDiff = Math.abs(offsetAdjust / 60) + expect(standardDate.getUTCFullYear()).toEqual(datetime.year) + expect(standardDate.getUTCMonth()).toEqual(datetime.month - 1) + expect(standardDate.getUTCDate()).toEqual(datetime.day) + const offsetAdjust = (datetime.timeZoneOffsetSeconds ?? 0) / 60 + const hourDiff = Math.abs((offsetAdjust - offsetAdjust % 60) / 60) const minuteDiff = Math.abs(offsetAdjust % 60) - expect(standardDate.getHours()).toBe(datetime.hour - hourDiff) - expect(standardDate.getMinutes()).toBe(datetime.minute - minuteDiff) - expect(standardDate.getSeconds()).toBe(datetime.second) - expect(standardDate.getMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000)) + expect(standardDate.getUTCHours()).toBe(datetime.hour - hourDiff) + expect(standardDate.getUTCMinutes()).toBe(datetime.minute - minuteDiff) + expect(standardDate.getUTCSeconds()).toBe(datetime.second) + expect(standardDate.getUTCMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000)) }) it('should not convert to a standard date (zoneid)', () => { @@ -153,12 +147,16 @@ describe('DateTime', () => { it('should be the reverse operation of fromStandardDate', () => { fc.assert( - fc.property(fc.date(), (date) => { - const datetime = DateTime.fromStandardDate(date) - const receivedDate = datetime.toStandardDate() + fc.property( + fc.date({ + max: temporalUtil.newDate(MAX_UTC_IN_MS - ONE_DAY_IN_MS), + min: temporalUtil.newDate(MIN_UTC_IN_MS + ONE_DAY_IN_MS) + }), (date) => { + const datetime = DateTime.fromStandardDate(date) + const receivedDate = datetime.toStandardDate() - expect(receivedDate).toEqual(date) - }) + expect(receivedDate).toEqual(date) + }) ) }) }) @@ -284,15 +282,3 @@ describe('isDateTime', () => { } }) }) - -/** - * The offset in StandardDate is the number of minutes - * to sum to the date and time to get the UTC time. - * - * This function change the sign of the offset, - * this way using the most common meaning. - * The time to add to UTC to get the local time. - */ -function offset (date: StandardDate): number { - return date.getTimezoneOffset() * -1 -} diff --git a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts index be1961ec8..52e4d2655 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts @@ -339,24 +339,54 @@ export function totalNanoseconds ( /** * Get the time zone offset in seconds from the given standard JavaScript date. * - * Implementation note: - * Time zone offset returned by the standard JavaScript date is the difference, in minutes, from local time to UTC. - * So positive value means offset is behind UTC and negative value means it is ahead. - * For Neo4j temporal types, like `Time` or `DateTime` offset is in seconds and represents difference from UTC to local time. - * This is different from standard JavaScript dates and that's why implementation negates the returned value. - * * @param {global.Date} standardDate the standard JavaScript date. * @return {number} the time zone offset in seconds. */ export function timeZoneOffsetInSeconds (standardDate: Date): number { - const secondsPortion = standardDate.getSeconds() >= standardDate.getUTCSeconds() - ? standardDate.getSeconds() - standardDate.getUTCSeconds() - : standardDate.getSeconds() - standardDate.getUTCSeconds() + 60 - const offsetInMinutes = standardDate.getTimezoneOffset() - if (offsetInMinutes === 0) { - return 0 + secondsPortion + const secondsPortion = standardDate.getSeconds() - standardDate.getUTCSeconds() + const minutesPortion = standardDate.getMinutes() - standardDate.getUTCMinutes() + const hoursPortion = standardDate.getHours() - standardDate.getUTCHours() + const daysPortion = _getDayOffset(standardDate) + return hoursPortion * SECONDS_PER_HOUR + minutesPortion * SECONDS_PER_MINUTE + secondsPortion + daysPortion * SECONDS_PER_DAY +} + +/** + * Get the difference in days from the given JavaScript date in local time and UTC. + * + * @private + * @param {global.Date} standardDate the date to evaluate + * @returns {number} the difference in days between date local time and UTC + */ +function _getDayOffset (standardDate: Date): number { + if (standardDate.getMonth() === standardDate.getUTCMonth()) { + return standardDate.getDate() - standardDate.getUTCDate() + } else if ((standardDate.getFullYear() > standardDate.getUTCFullYear()) || (standardDate.getMonth() > standardDate.getUTCMonth() && standardDate.getFullYear() === standardDate.getUTCFullYear())) { + return standardDate.getDate() + _daysUntilNextMonth(standardDate.getUTCMonth(), standardDate.getUTCFullYear()) - standardDate.getUTCDate() + } else { + return standardDate.getDate() - (standardDate.getUTCDate() + _daysUntilNextMonth(standardDate.getMonth(), standardDate.getFullYear())) + } +} + +/** + * Get the number of days in a month, including a check for leap years. + * + * @private + * @param {number} month the month of the date to evalutate + * @param {number} year the month of the date to evalutate + * @returns {number} the total number of days in the month evaluated + */ +function _daysUntilNextMonth (month: number, year: number): number { + if (month === 1) { + if (year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)) { + return 29 + } else { + return 28 + } + } else if ([0, 2, 4, 6, 7, 9, 11].includes(month)) { + return 31 + } else { + return 30 } - return -1 * offsetInMinutes * SECONDS_PER_MINUTE + secondsPortion } /** diff --git a/packages/neo4j-driver-deno/lib/core/temporal-types.ts b/packages/neo4j-driver-deno/lib/core/temporal-types.ts index 2461e3d1d..fbc48dd35 100644 --- a/packages/neo4j-driver-deno/lib/core/temporal-types.ts +++ b/packages/neo4j-driver-deno/lib/core/temporal-types.ts @@ -353,6 +353,10 @@ export class Date { /** * Create a {@link Date} object from the given standard JavaScript `Date`. * Hour, minute, second, millisecond and time zone offset components of the given date are ignored. + * + * NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0 + * If your timezone has a negative offset from UTC, creating a JavaScript Date at midnight UTC and converting it with {@link fromStandardDate} will result in a Date for the day before. + * * @param {global.Date} standardDate - The standard JavaScript date to convert. * @return {Date} New Date. */ @@ -372,6 +376,8 @@ export class Date { * The time component of the returned `Date` is set to midnight * and the time zone is set to UTC. * + * NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0 + * * @returns {StandardDate} Standard JavaScript `Date` at `00:00:00.000` UTC. */ toStandardDate (): StandardDate { diff --git a/packages/neo4j-driver/test/internal/temporal-util.test.js b/packages/neo4j-driver/test/internal/temporal-util.test.js index 7e768eb4a..6bf8427af 100644 --- a/packages/neo4j-driver/test/internal/temporal-util.test.js +++ b/packages/neo4j-driver/test/internal/temporal-util.test.js @@ -16,7 +16,6 @@ */ import { int, internal } from 'neo4j-driver-core' -import testUtils from './test-utils' const { temporalUtil: util } = internal @@ -261,27 +260,6 @@ describe('#unit temporal-util', () => { ).toEqual(BigInt(999000111)) }) - it('should get timezone offset in seconds from standard date', () => { - expect( - util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(0)) - ).toBe(0) - expect( - util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(2)) - ).toBe(-120) - expect( - util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(10)) - ).toBe(-600) - expect( - util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(101)) - ).toBe(-6060) - expect( - util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(-180)) - ).toBe(10800) - expect( - util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(-600)) - ).toBe(36000) - }) - it('should verify year', () => { expect(util.assertValidYear(-1)).toEqual(-1) expect(util.assertValidYear(-2010)).toEqual(-2010) diff --git a/packages/neo4j-driver/test/internal/test-utils.js b/packages/neo4j-driver/test/internal/test-utils.js index a244c48e0..689ecddd7 100644 --- a/packages/neo4j-driver/test/internal/test-utils.js +++ b/packages/neo4j-driver/test/internal/test-utils.js @@ -24,12 +24,6 @@ function isServer () { return !isClient() } -function fakeStandardDateWithOffset (offsetMinutes) { - const date = new Date() - date.getTimezoneOffset = () => offsetMinutes - return date -} - const matchers = { toBeElementOf: function (util, customEqualityTesters) { return { @@ -138,7 +132,6 @@ function spyProtocolWrite (protocol, callRealMethod = false) { export default { isClient, isServer, - fakeStandardDateWithOffset, matchers, MessageRecordingConnection, spyProtocolWrite diff --git a/packages/neo4j-driver/test/temporal-types.test.js b/packages/neo4j-driver/test/temporal-types.test.js index 67adc9fa0..3007397ea 100644 --- a/packages/neo4j-driver/test/temporal-types.test.js +++ b/packages/neo4j-driver/test/temporal-types.test.js @@ -18,7 +18,6 @@ import neo4j from '../src' import sharedNeo4j from './internal/shared-neo4j' import { toNumber, internal } from 'neo4j-driver-core' -import testUtils from './internal/test-utils' const { temporalUtil: { timeZoneOffsetInSeconds, totalNanoseconds } @@ -1350,50 +1349,6 @@ describe('#integration temporal-types', () => { ).toThrow() }, 90000) - it('should convert standard Date with offset to neo4j Time', () => { - const standardDate1 = testUtils.fakeStandardDateWithOffset(0) - const neo4jTime1 = neo4j.types.Time.fromStandardDate(standardDate1) - verifyTimeZoneOffset(neo4jTime1, 0, 'Z') - - const standardDate2 = testUtils.fakeStandardDateWithOffset(-600) - const neo4jTime2 = neo4j.types.Time.fromStandardDate(standardDate2) - verifyTimeZoneOffset(neo4jTime2, 600 * 60, '+10:00') - - const standardDate3 = testUtils.fakeStandardDateWithOffset(480) - const neo4jTime3 = neo4j.types.Time.fromStandardDate(standardDate3) - verifyTimeZoneOffset(neo4jTime3, -1 * 480 * 60, '-08:00') - - const standardDate4 = testUtils.fakeStandardDateWithOffset(-180) - const neo4jTime4 = neo4j.types.Time.fromStandardDate(standardDate4) - verifyTimeZoneOffset(neo4jTime4, 180 * 60, '+03:00') - - const standardDate5 = testUtils.fakeStandardDateWithOffset(150) - const neo4jTime5 = neo4j.types.Time.fromStandardDate(standardDate5) - verifyTimeZoneOffset(neo4jTime5, -1 * 150 * 60, '-02:30') - }, 90000) - - it('should convert standard Date with offset to neo4j DateTime', () => { - const standardDate1 = testUtils.fakeStandardDateWithOffset(0) - const neo4jDateTime1 = neo4j.types.DateTime.fromStandardDate(standardDate1) - verifyTimeZoneOffset(neo4jDateTime1, 0, 'Z') - - const standardDate2 = testUtils.fakeStandardDateWithOffset(-600) - const neo4jDateTime2 = neo4j.types.DateTime.fromStandardDate(standardDate2) - verifyTimeZoneOffset(neo4jDateTime2, 600 * 60, '+10:00') - - const standardDate3 = testUtils.fakeStandardDateWithOffset(480) - const neo4jDateTime3 = neo4j.types.DateTime.fromStandardDate(standardDate3) - verifyTimeZoneOffset(neo4jDateTime3, -1 * 480 * 60, '-08:00') - - const standardDate4 = testUtils.fakeStandardDateWithOffset(-180) - const neo4jDateTime4 = neo4j.types.DateTime.fromStandardDate(standardDate4) - verifyTimeZoneOffset(neo4jDateTime4, 180 * 60, '+03:00') - - const standardDate5 = testUtils.fakeStandardDateWithOffset(150) - const neo4jDateTime5 = neo4j.types.DateTime.fromStandardDate(standardDate5) - verifyTimeZoneOffset(neo4jDateTime5, -1 * 150 * 60, '-02:30') - }, 90000) - it('should not create DateTime with invalid ZoneId', () => { expect(() => dateTimeWithZoneId(1999, 10, 1, 10, 15, 0, 0, 'Europe/Neo4j')).toThrowError( 'Time zone ID is expected to be a valid ZoneId but was: "Europe/Neo4j"' @@ -1803,16 +1758,4 @@ describe('#integration temporal-types', () => { ) expect(converted).toEqual(expected) } - - function verifyTimeZoneOffset (temporal, expectedValue, expectedStringValue) { - expect(temporal.timeZoneOffsetSeconds).toEqual(expectedValue) - const isoString = temporal.toString() - // assert ISO string ends with the expected suffix - expect( - isoString.indexOf( - expectedStringValue, - isoString.length - expectedStringValue.length - ) - ).toBeGreaterThan(0) - } }) From 549e7a90508688469db48024824ecb5ddf0d678d Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:42:10 +0100 Subject: [PATCH 12/22] Revert "fix documentation of supportsUserImpersonation (#1241)" This reverts commit 3104a297238a4d6a9f78e4722f9118190390c4c6. --- packages/core/src/connection-provider.ts | 2 +- packages/neo4j-driver-deno/lib/core/connection-provider.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index e65795b41..0cfacc1d4 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -91,7 +91,7 @@ class ConnectionProvider { } /** - * This method checks whether the backend database supports user impersonation functionality + * This method checks whether the backend database supports transaction config functionality * by checking protocol handshake result. * * @returns {Promise} diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index ebc4803bf..977aeeada 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -91,7 +91,7 @@ class ConnectionProvider { } /** - * This method checks whether the backend database supports user impersonation functionality + * This method checks whether the backend database supports transaction config functionality * by checking protocol handshake result. * * @returns {Promise} From 0d5fbaecff830d593d581a2d6ec2747befcf3b36 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:42:13 +0100 Subject: [PATCH 13/22] Revert "fixing minute offset issue in certain time zones (#1238)" This reverts commit ff3dd5f8fc1356a5136243cff5283aa60eff93f1. --- packages/bolt-connection/test/test-utils.js | 7 ++ packages/core/src/internal/temporal-util.ts | 56 +++---------- packages/core/src/temporal-types.ts | 6 -- packages/core/test/temporal-types.test.ts | 84 +++++++++++-------- .../lib/core/internal/temporal-util.ts | 56 +++---------- .../lib/core/temporal-types.ts | 6 -- .../test/internal/temporal-util.test.js | 22 +++++ .../neo4j-driver/test/internal/test-utils.js | 7 ++ .../neo4j-driver/test/temporal-types.test.js | 57 +++++++++++++ 9 files changed, 168 insertions(+), 133 deletions(-) diff --git a/packages/bolt-connection/test/test-utils.js b/packages/bolt-connection/test/test-utils.js index c0d225199..9fd6c03a8 100644 --- a/packages/bolt-connection/test/test-utils.js +++ b/packages/bolt-connection/test/test-utils.js @@ -31,6 +31,12 @@ function isServer () { return !isClient() } +function fakeStandardDateWithOffset (offsetMinutes) { + const date = new Date() + date.getTimezoneOffset = () => offsetMinutes + return date +} + const matchers = { toBeElementOf: function (actual, expected) { if (expected === undefined) { @@ -155,6 +161,7 @@ function arbitraryTimeZoneId () { export default { isClient, isServer, + fakeStandardDateWithOffset, matchers, MessageRecordingConnection, spyProtocolWrite, diff --git a/packages/core/src/internal/temporal-util.ts b/packages/core/src/internal/temporal-util.ts index ece38c424..2951d5ac5 100644 --- a/packages/core/src/internal/temporal-util.ts +++ b/packages/core/src/internal/temporal-util.ts @@ -339,54 +339,24 @@ export function totalNanoseconds ( /** * Get the time zone offset in seconds from the given standard JavaScript date. * + * Implementation note: + * Time zone offset returned by the standard JavaScript date is the difference, in minutes, from local time to UTC. + * So positive value means offset is behind UTC and negative value means it is ahead. + * For Neo4j temporal types, like `Time` or `DateTime` offset is in seconds and represents difference from UTC to local time. + * This is different from standard JavaScript dates and that's why implementation negates the returned value. + * * @param {global.Date} standardDate the standard JavaScript date. * @return {number} the time zone offset in seconds. */ export function timeZoneOffsetInSeconds (standardDate: Date): number { - const secondsPortion = standardDate.getSeconds() - standardDate.getUTCSeconds() - const minutesPortion = standardDate.getMinutes() - standardDate.getUTCMinutes() - const hoursPortion = standardDate.getHours() - standardDate.getUTCHours() - const daysPortion = _getDayOffset(standardDate) - return hoursPortion * SECONDS_PER_HOUR + minutesPortion * SECONDS_PER_MINUTE + secondsPortion + daysPortion * SECONDS_PER_DAY -} - -/** - * Get the difference in days from the given JavaScript date in local time and UTC. - * - * @private - * @param {global.Date} standardDate the date to evaluate - * @returns {number} the difference in days between date local time and UTC - */ -function _getDayOffset (standardDate: Date): number { - if (standardDate.getMonth() === standardDate.getUTCMonth()) { - return standardDate.getDate() - standardDate.getUTCDate() - } else if ((standardDate.getFullYear() > standardDate.getUTCFullYear()) || (standardDate.getMonth() > standardDate.getUTCMonth() && standardDate.getFullYear() === standardDate.getUTCFullYear())) { - return standardDate.getDate() + _daysUntilNextMonth(standardDate.getUTCMonth(), standardDate.getUTCFullYear()) - standardDate.getUTCDate() - } else { - return standardDate.getDate() - (standardDate.getUTCDate() + _daysUntilNextMonth(standardDate.getMonth(), standardDate.getFullYear())) - } -} - -/** - * Get the number of days in a month, including a check for leap years. - * - * @private - * @param {number} month the month of the date to evalutate - * @param {number} year the month of the date to evalutate - * @returns {number} the total number of days in the month evaluated - */ -function _daysUntilNextMonth (month: number, year: number): number { - if (month === 1) { - if (year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)) { - return 29 - } else { - return 28 - } - } else if ([0, 2, 4, 6, 7, 9, 11].includes(month)) { - return 31 - } else { - return 30 + const secondsPortion = standardDate.getSeconds() >= standardDate.getUTCSeconds() + ? standardDate.getSeconds() - standardDate.getUTCSeconds() + : standardDate.getSeconds() - standardDate.getUTCSeconds() + 60 + const offsetInMinutes = standardDate.getTimezoneOffset() + if (offsetInMinutes === 0) { + return 0 + secondsPortion } + return -1 * offsetInMinutes * SECONDS_PER_MINUTE + secondsPortion } /** diff --git a/packages/core/src/temporal-types.ts b/packages/core/src/temporal-types.ts index 7ea533506..a9837d2d0 100644 --- a/packages/core/src/temporal-types.ts +++ b/packages/core/src/temporal-types.ts @@ -353,10 +353,6 @@ export class Date { /** * Create a {@link Date} object from the given standard JavaScript `Date`. * Hour, minute, second, millisecond and time zone offset components of the given date are ignored. - * - * NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0 - * If your timezone has a negative offset from UTC, creating a JavaScript Date at midnight UTC and converting it with {@link fromStandardDate} will result in a Date for the day before. - * * @param {global.Date} standardDate - The standard JavaScript date to convert. * @return {Date} New Date. */ @@ -376,8 +372,6 @@ export class Date { * The time component of the returned `Date` is set to midnight * and the time zone is set to UTC. * - * NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0 - * * @returns {StandardDate} Standard JavaScript `Date` at `00:00:00.000` UTC. */ toStandardDate (): StandardDate { diff --git a/packages/core/test/temporal-types.test.ts b/packages/core/test/temporal-types.test.ts index 4341be584..d51d0057d 100644 --- a/packages/core/test/temporal-types.test.ts +++ b/packages/core/test/temporal-types.test.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { StandardDate } from '../src/graph-types' import { LocalDateTime, Date, DateTime, Duration, isDuration, LocalTime, isLocalTime, Time, isTime, isDate, isLocalDateTime, isDateTime } from '../src/temporal-types' import { temporalUtil } from '../src/internal' import fc from 'fast-check' @@ -30,9 +31,9 @@ describe('Date', () => { const standardDate = localDatetime.toStandardDate() - expect(standardDate.getUTCFullYear()).toEqual(localDatetime.year) - expect(standardDate.getUTCMonth()).toEqual(localDatetime.month - 1) - expect(standardDate.getUTCDate()).toEqual(localDatetime.day) + expect(standardDate.getFullYear()).toEqual(localDatetime.year) + expect(standardDate.getMonth()).toEqual(localDatetime.month - 1) + expect(standardDate.getDate()).toEqual(localDatetime.day) }) it('should be the reverse operation of fromStandardDate but losing time information', () => { @@ -46,11 +47,14 @@ describe('Date', () => { const date = Date.fromStandardDate(standardDate) const receivedDate = date.toStandardDate() - expect(receivedDate.getUTCFullYear()).toEqual(standardDate.getFullYear()) // Date converts from local time but to UTC - expect(receivedDate.getUTCMonth()).toEqual(standardDate.getMonth()) - expect(receivedDate.getUTCDate()).toEqual(standardDate.getDate()) - expect(receivedDate.getUTCHours()).toEqual(0) - expect(receivedDate.getUTCMinutes()).toEqual(0) + const adjustedDateTime = temporalUtil.newDate(standardDate) + adjustedDateTime.setHours(0, offset(receivedDate)) + + expect(receivedDate.getFullYear()).toEqual(adjustedDateTime.getFullYear()) + expect(receivedDate.getMonth()).toEqual(adjustedDateTime.getMonth()) + expect(receivedDate.getDate()).toEqual(adjustedDateTime.getDate()) + expect(receivedDate.getHours()).toEqual(adjustedDateTime.getHours()) + expect(receivedDate.getMinutes()).toEqual(adjustedDateTime.getMinutes()) }) ) }) @@ -109,16 +113,17 @@ describe('DateTime', () => { const standardDate = datetime.toStandardDate() - expect(standardDate.getUTCFullYear()).toEqual(datetime.year) - expect(standardDate.getUTCMonth()).toEqual(datetime.month - 1) - expect(standardDate.getUTCDate()).toEqual(datetime.day) // The datetime in this test will never cross the date line in conversion, it is therefore safe to use UTC here to avoid machine timezone from altering the result of the test. - const offsetAdjust = (datetime.timeZoneOffsetSeconds ?? 0) / 60 - const hourDiff = Math.abs((offsetAdjust - offsetAdjust % 60) / 60) + expect(standardDate.getFullYear()).toEqual(datetime.year) + expect(standardDate.getMonth()).toEqual(datetime.month - 1) + expect(standardDate.getDate()).toEqual(datetime.day) + const offsetInMinutes = offset(standardDate) + const offsetAdjust = offsetInMinutes - (datetime.timeZoneOffsetSeconds ?? 0) / 60 + const hourDiff = Math.abs(offsetAdjust / 60) const minuteDiff = Math.abs(offsetAdjust % 60) - expect(standardDate.getUTCHours()).toBe(datetime.hour - hourDiff) - expect(standardDate.getUTCMinutes()).toBe(datetime.minute - minuteDiff) - expect(standardDate.getUTCSeconds()).toBe(datetime.second) - expect(standardDate.getUTCMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000)) + expect(standardDate.getHours()).toBe(datetime.hour - hourDiff) + expect(standardDate.getMinutes()).toBe(datetime.minute - minuteDiff) + expect(standardDate.getSeconds()).toBe(datetime.second) + expect(standardDate.getMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000)) }) it('should convert to a standard date (offset)', () => { @@ -126,16 +131,17 @@ describe('DateTime', () => { const standardDate = datetime.toStandardDate() - expect(standardDate.getUTCFullYear()).toEqual(datetime.year) - expect(standardDate.getUTCMonth()).toEqual(datetime.month - 1) - expect(standardDate.getUTCDate()).toEqual(datetime.day) - const offsetAdjust = (datetime.timeZoneOffsetSeconds ?? 0) / 60 - const hourDiff = Math.abs((offsetAdjust - offsetAdjust % 60) / 60) + expect(standardDate.getFullYear()).toEqual(datetime.year) + expect(standardDate.getMonth()).toEqual(datetime.month - 1) + expect(standardDate.getDate()).toEqual(datetime.day) + const offsetInMinutes = offset(standardDate) + const offsetAdjust = offsetInMinutes - (datetime.timeZoneOffsetSeconds ?? 0) / 60 + const hourDiff = Math.abs(offsetAdjust / 60) const minuteDiff = Math.abs(offsetAdjust % 60) - expect(standardDate.getUTCHours()).toBe(datetime.hour - hourDiff) - expect(standardDate.getUTCMinutes()).toBe(datetime.minute - minuteDiff) - expect(standardDate.getUTCSeconds()).toBe(datetime.second) - expect(standardDate.getUTCMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000)) + expect(standardDate.getHours()).toBe(datetime.hour - hourDiff) + expect(standardDate.getMinutes()).toBe(datetime.minute - minuteDiff) + expect(standardDate.getSeconds()).toBe(datetime.second) + expect(standardDate.getMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000)) }) it('should not convert to a standard date (zoneid)', () => { @@ -147,16 +153,12 @@ describe('DateTime', () => { it('should be the reverse operation of fromStandardDate', () => { fc.assert( - fc.property( - fc.date({ - max: temporalUtil.newDate(MAX_UTC_IN_MS - ONE_DAY_IN_MS), - min: temporalUtil.newDate(MIN_UTC_IN_MS + ONE_DAY_IN_MS) - }), (date) => { - const datetime = DateTime.fromStandardDate(date) - const receivedDate = datetime.toStandardDate() + fc.property(fc.date(), (date) => { + const datetime = DateTime.fromStandardDate(date) + const receivedDate = datetime.toStandardDate() - expect(receivedDate).toEqual(date) - }) + expect(receivedDate).toEqual(date) + }) ) }) }) @@ -282,3 +284,15 @@ describe('isDateTime', () => { } }) }) + +/** + * The offset in StandardDate is the number of minutes + * to sum to the date and time to get the UTC time. + * + * This function change the sign of the offset, + * this way using the most common meaning. + * The time to add to UTC to get the local time. + */ +function offset (date: StandardDate): number { + return date.getTimezoneOffset() * -1 +} diff --git a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts index 52e4d2655..be1961ec8 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts @@ -339,54 +339,24 @@ export function totalNanoseconds ( /** * Get the time zone offset in seconds from the given standard JavaScript date. * + * Implementation note: + * Time zone offset returned by the standard JavaScript date is the difference, in minutes, from local time to UTC. + * So positive value means offset is behind UTC and negative value means it is ahead. + * For Neo4j temporal types, like `Time` or `DateTime` offset is in seconds and represents difference from UTC to local time. + * This is different from standard JavaScript dates and that's why implementation negates the returned value. + * * @param {global.Date} standardDate the standard JavaScript date. * @return {number} the time zone offset in seconds. */ export function timeZoneOffsetInSeconds (standardDate: Date): number { - const secondsPortion = standardDate.getSeconds() - standardDate.getUTCSeconds() - const minutesPortion = standardDate.getMinutes() - standardDate.getUTCMinutes() - const hoursPortion = standardDate.getHours() - standardDate.getUTCHours() - const daysPortion = _getDayOffset(standardDate) - return hoursPortion * SECONDS_PER_HOUR + minutesPortion * SECONDS_PER_MINUTE + secondsPortion + daysPortion * SECONDS_PER_DAY -} - -/** - * Get the difference in days from the given JavaScript date in local time and UTC. - * - * @private - * @param {global.Date} standardDate the date to evaluate - * @returns {number} the difference in days between date local time and UTC - */ -function _getDayOffset (standardDate: Date): number { - if (standardDate.getMonth() === standardDate.getUTCMonth()) { - return standardDate.getDate() - standardDate.getUTCDate() - } else if ((standardDate.getFullYear() > standardDate.getUTCFullYear()) || (standardDate.getMonth() > standardDate.getUTCMonth() && standardDate.getFullYear() === standardDate.getUTCFullYear())) { - return standardDate.getDate() + _daysUntilNextMonth(standardDate.getUTCMonth(), standardDate.getUTCFullYear()) - standardDate.getUTCDate() - } else { - return standardDate.getDate() - (standardDate.getUTCDate() + _daysUntilNextMonth(standardDate.getMonth(), standardDate.getFullYear())) - } -} - -/** - * Get the number of days in a month, including a check for leap years. - * - * @private - * @param {number} month the month of the date to evalutate - * @param {number} year the month of the date to evalutate - * @returns {number} the total number of days in the month evaluated - */ -function _daysUntilNextMonth (month: number, year: number): number { - if (month === 1) { - if (year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)) { - return 29 - } else { - return 28 - } - } else if ([0, 2, 4, 6, 7, 9, 11].includes(month)) { - return 31 - } else { - return 30 + const secondsPortion = standardDate.getSeconds() >= standardDate.getUTCSeconds() + ? standardDate.getSeconds() - standardDate.getUTCSeconds() + : standardDate.getSeconds() - standardDate.getUTCSeconds() + 60 + const offsetInMinutes = standardDate.getTimezoneOffset() + if (offsetInMinutes === 0) { + return 0 + secondsPortion } + return -1 * offsetInMinutes * SECONDS_PER_MINUTE + secondsPortion } /** diff --git a/packages/neo4j-driver-deno/lib/core/temporal-types.ts b/packages/neo4j-driver-deno/lib/core/temporal-types.ts index fbc48dd35..2461e3d1d 100644 --- a/packages/neo4j-driver-deno/lib/core/temporal-types.ts +++ b/packages/neo4j-driver-deno/lib/core/temporal-types.ts @@ -353,10 +353,6 @@ export class Date { /** * Create a {@link Date} object from the given standard JavaScript `Date`. * Hour, minute, second, millisecond and time zone offset components of the given date are ignored. - * - * NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0 - * If your timezone has a negative offset from UTC, creating a JavaScript Date at midnight UTC and converting it with {@link fromStandardDate} will result in a Date for the day before. - * * @param {global.Date} standardDate - The standard JavaScript date to convert. * @return {Date} New Date. */ @@ -376,8 +372,6 @@ export class Date { * The time component of the returned `Date` is set to midnight * and the time zone is set to UTC. * - * NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0 - * * @returns {StandardDate} Standard JavaScript `Date` at `00:00:00.000` UTC. */ toStandardDate (): StandardDate { diff --git a/packages/neo4j-driver/test/internal/temporal-util.test.js b/packages/neo4j-driver/test/internal/temporal-util.test.js index 6bf8427af..7e768eb4a 100644 --- a/packages/neo4j-driver/test/internal/temporal-util.test.js +++ b/packages/neo4j-driver/test/internal/temporal-util.test.js @@ -16,6 +16,7 @@ */ import { int, internal } from 'neo4j-driver-core' +import testUtils from './test-utils' const { temporalUtil: util } = internal @@ -260,6 +261,27 @@ describe('#unit temporal-util', () => { ).toEqual(BigInt(999000111)) }) + it('should get timezone offset in seconds from standard date', () => { + expect( + util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(0)) + ).toBe(0) + expect( + util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(2)) + ).toBe(-120) + expect( + util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(10)) + ).toBe(-600) + expect( + util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(101)) + ).toBe(-6060) + expect( + util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(-180)) + ).toBe(10800) + expect( + util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(-600)) + ).toBe(36000) + }) + it('should verify year', () => { expect(util.assertValidYear(-1)).toEqual(-1) expect(util.assertValidYear(-2010)).toEqual(-2010) diff --git a/packages/neo4j-driver/test/internal/test-utils.js b/packages/neo4j-driver/test/internal/test-utils.js index 689ecddd7..a244c48e0 100644 --- a/packages/neo4j-driver/test/internal/test-utils.js +++ b/packages/neo4j-driver/test/internal/test-utils.js @@ -24,6 +24,12 @@ function isServer () { return !isClient() } +function fakeStandardDateWithOffset (offsetMinutes) { + const date = new Date() + date.getTimezoneOffset = () => offsetMinutes + return date +} + const matchers = { toBeElementOf: function (util, customEqualityTesters) { return { @@ -132,6 +138,7 @@ function spyProtocolWrite (protocol, callRealMethod = false) { export default { isClient, isServer, + fakeStandardDateWithOffset, matchers, MessageRecordingConnection, spyProtocolWrite diff --git a/packages/neo4j-driver/test/temporal-types.test.js b/packages/neo4j-driver/test/temporal-types.test.js index 3007397ea..67adc9fa0 100644 --- a/packages/neo4j-driver/test/temporal-types.test.js +++ b/packages/neo4j-driver/test/temporal-types.test.js @@ -18,6 +18,7 @@ import neo4j from '../src' import sharedNeo4j from './internal/shared-neo4j' import { toNumber, internal } from 'neo4j-driver-core' +import testUtils from './internal/test-utils' const { temporalUtil: { timeZoneOffsetInSeconds, totalNanoseconds } @@ -1349,6 +1350,50 @@ describe('#integration temporal-types', () => { ).toThrow() }, 90000) + it('should convert standard Date with offset to neo4j Time', () => { + const standardDate1 = testUtils.fakeStandardDateWithOffset(0) + const neo4jTime1 = neo4j.types.Time.fromStandardDate(standardDate1) + verifyTimeZoneOffset(neo4jTime1, 0, 'Z') + + const standardDate2 = testUtils.fakeStandardDateWithOffset(-600) + const neo4jTime2 = neo4j.types.Time.fromStandardDate(standardDate2) + verifyTimeZoneOffset(neo4jTime2, 600 * 60, '+10:00') + + const standardDate3 = testUtils.fakeStandardDateWithOffset(480) + const neo4jTime3 = neo4j.types.Time.fromStandardDate(standardDate3) + verifyTimeZoneOffset(neo4jTime3, -1 * 480 * 60, '-08:00') + + const standardDate4 = testUtils.fakeStandardDateWithOffset(-180) + const neo4jTime4 = neo4j.types.Time.fromStandardDate(standardDate4) + verifyTimeZoneOffset(neo4jTime4, 180 * 60, '+03:00') + + const standardDate5 = testUtils.fakeStandardDateWithOffset(150) + const neo4jTime5 = neo4j.types.Time.fromStandardDate(standardDate5) + verifyTimeZoneOffset(neo4jTime5, -1 * 150 * 60, '-02:30') + }, 90000) + + it('should convert standard Date with offset to neo4j DateTime', () => { + const standardDate1 = testUtils.fakeStandardDateWithOffset(0) + const neo4jDateTime1 = neo4j.types.DateTime.fromStandardDate(standardDate1) + verifyTimeZoneOffset(neo4jDateTime1, 0, 'Z') + + const standardDate2 = testUtils.fakeStandardDateWithOffset(-600) + const neo4jDateTime2 = neo4j.types.DateTime.fromStandardDate(standardDate2) + verifyTimeZoneOffset(neo4jDateTime2, 600 * 60, '+10:00') + + const standardDate3 = testUtils.fakeStandardDateWithOffset(480) + const neo4jDateTime3 = neo4j.types.DateTime.fromStandardDate(standardDate3) + verifyTimeZoneOffset(neo4jDateTime3, -1 * 480 * 60, '-08:00') + + const standardDate4 = testUtils.fakeStandardDateWithOffset(-180) + const neo4jDateTime4 = neo4j.types.DateTime.fromStandardDate(standardDate4) + verifyTimeZoneOffset(neo4jDateTime4, 180 * 60, '+03:00') + + const standardDate5 = testUtils.fakeStandardDateWithOffset(150) + const neo4jDateTime5 = neo4j.types.DateTime.fromStandardDate(standardDate5) + verifyTimeZoneOffset(neo4jDateTime5, -1 * 150 * 60, '-02:30') + }, 90000) + it('should not create DateTime with invalid ZoneId', () => { expect(() => dateTimeWithZoneId(1999, 10, 1, 10, 15, 0, 0, 'Europe/Neo4j')).toThrowError( 'Time zone ID is expected to be a valid ZoneId but was: "Europe/Neo4j"' @@ -1758,4 +1803,16 @@ describe('#integration temporal-types', () => { ) expect(converted).toEqual(expected) } + + function verifyTimeZoneOffset (temporal, expectedValue, expectedStringValue) { + expect(temporal.timeZoneOffsetSeconds).toEqual(expectedValue) + const isoString = temporal.toString() + // assert ISO string ends with the expected suffix + expect( + isoString.indexOf( + expectedStringValue, + isoString.length - expectedStringValue.length + ) + ).toBeGreaterThan(0) + } }) From 01ccacf0710ebe234f36dd15014b84a1a4e1a523 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:27:14 +0100 Subject: [PATCH 14/22] Version sorting the right way around :facepalm: --- packages/bolt-connection/src/bolt/handshake.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index aba4429db..aefa98320 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -91,7 +91,7 @@ function handshakeNegotiationV2 (channel, buffer, log) { // select preferrable protocol and respond let major let minor - versions.sort((a, b) => Number(a.major + '.' + a.minor) - Number(b.major + '.' + b.minor)) + versions.sort((a, b) => Number(b.major + '.' + b.minor) - Number(a.major + '.' + a.minor)) for (let i = 0; i < versions.length; i++) { const version = versions[i] if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) { From c21a5f16f85161eae42b60c417020a8731714063 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:34:20 +0100 Subject: [PATCH 15/22] deno --- .../neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index 481f2c6fb..235fdc4b5 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -91,7 +91,7 @@ function handshakeNegotiationV2 (channel, buffer, log) { // select preferrable protocol and respond let major let minor - versions.sort((a, b) => Number(a.major + '.' + a.minor) - Number(b.major + '.' + b.minor)) + versions.sort((a, b) => Number(b.major + '.' + b.minor) - Number(a.major + '.' + a.minor)) for (let i = 0; i < versions.length; i++) { const version = versions[i] if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) { From a43f3719d3e57999cf2e78adca101072a303e8ff Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:11:38 +0100 Subject: [PATCH 16/22] change available protocols --- packages/bolt-connection/src/bolt/handshake.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index 4ad2d8ff0..61384a4a5 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -19,7 +19,7 @@ import { alloc } from '../channel' import { newError } from 'neo4j-driver-core' const BOLT_MAGIC_PREAMBLE = 0x6060b017 -const AVAILABLE_BOLT_PROTOCOLS = [5.7, 5.6, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 4.1, 4.0, 3.0] // bolt protocols the client supports, ordered by preference +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 supports, ordered by preference const DESIRED_CAPABILITES = 0 function version (major, minor) { From 845998213d86c8b7547eba0eb9fd2a68cf07f607 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:39:03 +0100 Subject: [PATCH 17/22] fix unit tests --- packages/bolt-connection/test/bolt/index.test.js | 2 +- .../neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bolt-connection/test/bolt/index.test.js b/packages/bolt-connection/test/bolt/index.test.js index 837a77e2e..3ebdac880 100644 --- a/packages/bolt-connection/test/bolt/index.test.js +++ b/packages/bolt-connection/test/bolt/index.test.js @@ -54,7 +54,7 @@ describe('#unit Bolt', () => { const protocolVersion3 = '00 00 00 03' expect(writtenBuffer.toHex()).toEqual( - `${boltMagicPreamble} ${handshakev2} ${protocolVersion5x7to5x0} ${protocolVersion4x4to4x0} ${protocolVersion3}` + `${boltMagicPreamble} ${handshakev2} ${protocolVersion5x7to5x0} ${protocolVersion4x4to4x2} ${protocolVersion3}` ) }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index c9537ffa8..c65b9d40e 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -19,7 +19,7 @@ import { alloc } from '../channel/index.js' import { newError } from '../../core/index.ts' const BOLT_MAGIC_PREAMBLE = 0x6060b017 -const AVAILABLE_BOLT_PROTOCOLS = [5.7, 5.6, 5.4, 5.3, 5.2, 5.1, 5.0, 4.4, 4.3, 4.2, 4.1, 4.0, 3.0] // bolt protocols the client supports, ordered by preference +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 supports, ordered by preference const DESIRED_CAPABILITES = 0 function version (major, minor) { From 21453b1a271ddf540bf38e620a669d55582cbaae Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 29 Jan 2025 08:54:05 +0100 Subject: [PATCH 18/22] return to main testkit branch --- testkit/testkit.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testkit/testkit.json b/testkit/testkit.json index 9e496140c..931900356 100644 --- a/testkit/testkit.json +++ b/testkit/testkit.json @@ -1,6 +1,6 @@ { "testkit": { "uri": "https://github.com/neo4j-drivers/testkit.git", - "ref": "bolt-handshake-v2" + "ref": "5.0" } } From 4d6bf912edc78c26c8b77635f78a04d16a97db50 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:34:44 +0100 Subject: [PATCH 19/22] minor test correction and buffer refactor --- packages/bolt-connection/src/buf/base-buf.js | 24 ++++++++++++------- .../bolt-connection/test/bolt/index.test.js | 4 ++-- .../lib/bolt-connection/buf/base-buf.js | 24 ++++++++++++------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/packages/bolt-connection/src/buf/base-buf.js b/packages/bolt-connection/src/buf/base-buf.js index 88dcf37da..d7f86ea1f 100644 --- a/packages/bolt-connection/src/buf/base-buf.js +++ b/packages/bolt-connection/src/buf/base-buf.js @@ -182,6 +182,20 @@ export default class BaseBuffer { this.putUInt8(p + 7, val & 0xff) } + putVarInt (p, val) { + let length = 0 + while (val > 1) { + let int = val % 128 + if (val >= 128) { + int += 128 + } + val = val / 128 + this.putInt8(p + length, int) + length += 1 + } + return length + } + /** * @param position * @param other @@ -314,14 +328,8 @@ export default class BaseBuffer { } writeVarInt (val) { - while (val > 1) { - let int = val % 128 - if (val >= 128) { - int += 128 - } - val = val / 128 - this.putInt8(this._updatePos(1), int) - } + const length = this.putVarInt(this.position, val) + this._updatePos(length) } /** diff --git a/packages/bolt-connection/test/bolt/index.test.js b/packages/bolt-connection/test/bolt/index.test.js index 3ebdac880..6fb17e146 100644 --- a/packages/bolt-connection/test/bolt/index.test.js +++ b/packages/bolt-connection/test/bolt/index.test.js @@ -49,12 +49,12 @@ describe('#unit Bolt', () => { const boltMagicPreamble = '60 60 b0 17' const handshakev2 = '00 00 01 ff' - const protocolVersion5x7to5x0 = '00 08 08 05' + const protocolVersion5x8to5x0 = '00 08 08 05' const protocolVersion4x4to4x2 = '00 02 04 04' const protocolVersion3 = '00 00 00 03' expect(writtenBuffer.toHex()).toEqual( - `${boltMagicPreamble} ${handshakev2} ${protocolVersion5x7to5x0} ${protocolVersion4x4to4x2} ${protocolVersion3}` + `${boltMagicPreamble} ${handshakev2} ${protocolVersion5x8to5x0} ${protocolVersion4x4to4x2} ${protocolVersion3}` ) }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js b/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js index 88dcf37da..d7f86ea1f 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js @@ -182,6 +182,20 @@ export default class BaseBuffer { this.putUInt8(p + 7, val & 0xff) } + putVarInt (p, val) { + let length = 0 + while (val > 1) { + let int = val % 128 + if (val >= 128) { + int += 128 + } + val = val / 128 + this.putInt8(p + length, int) + length += 1 + } + return length + } + /** * @param position * @param other @@ -314,14 +328,8 @@ export default class BaseBuffer { } writeVarInt (val) { - while (val > 1) { - let int = val % 128 - if (val >= 128) { - int += 128 - } - val = val / 128 - this.putInt8(this._updatePos(1), int) - } + const length = this.putVarInt(this.position, val) + this._updatePos(length) } /** From 519e989dbf65bb1902f5afe8ee95c69b58af970c Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:54:58 +0100 Subject: [PATCH 20/22] switch to uint --- packages/bolt-connection/src/buf/base-buf.js | 2 +- packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bolt-connection/src/buf/base-buf.js b/packages/bolt-connection/src/buf/base-buf.js index d7f86ea1f..871d8c996 100644 --- a/packages/bolt-connection/src/buf/base-buf.js +++ b/packages/bolt-connection/src/buf/base-buf.js @@ -190,7 +190,7 @@ export default class BaseBuffer { int += 128 } val = val / 128 - this.putInt8(p + length, int) + this.putUInt8(p + length, int) length += 1 } return length diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js b/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js index d7f86ea1f..871d8c996 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/buf/base-buf.js @@ -190,7 +190,7 @@ export default class BaseBuffer { int += 128 } val = val / 128 - this.putInt8(p + length, int) + this.putUInt8(p + length, int) length += 1 } return length From 8dd8c1c557c4c4f2516943f3775069ef885f9b79 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 30 Jan 2025 08:27:21 +0100 Subject: [PATCH 21/22] comment changes --- packages/bolt-connection/src/bolt/handshake.js | 4 +--- .../neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index 61384a4a5..eff9665cb 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -19,7 +19,7 @@ import { alloc } from '../channel' import { newError } from 'neo4j-driver-core' const BOLT_MAGIC_PREAMBLE = 0x6060b017 -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 supports, ordered by preference +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 const DESIRED_CAPABILITES = 0 function version (major, minor) { @@ -87,8 +87,6 @@ function handshakeNegotiationV2 (channel, buffer, log) { const capabilityBitMask = buffer.readVarInt() const capabilites = selectCapabilites(capabilityBitMask) - // parse supported capabilities - // select preferrable protocol and respond let major let minor versions.sort((a, b) => Number(b.major + '.' + b.minor) - Number(a.major + '.' + a.minor)) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index c65b9d40e..5e5f23423 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -19,7 +19,7 @@ import { alloc } from '../channel/index.js' import { newError } from '../../core/index.ts' const BOLT_MAGIC_PREAMBLE = 0x6060b017 -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 supports, ordered by preference +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 const DESIRED_CAPABILITES = 0 function version (major, minor) { @@ -87,8 +87,6 @@ function handshakeNegotiationV2 (channel, buffer, log) { const capabilityBitMask = buffer.readVarInt() const capabilites = selectCapabilites(capabilityBitMask) - // parse supported capabilities - // select preferrable protocol and respond let major let minor versions.sort((a, b) => Number(b.major + '.' + b.minor) - Number(a.major + '.' + a.minor)) From 53c9356cb099730bdb5e488fbecf0726b05d00f7 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:58:36 +0100 Subject: [PATCH 22/22] improved sort and safety in new handshake --- packages/bolt-connection/src/bolt/handshake.js | 12 +++++++++--- .../lib/bolt-connection/bolt/handshake.js | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index eff9665cb..47c26def7 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -87,9 +87,15 @@ function handshakeNegotiationV2 (channel, buffer, log) { const capabilityBitMask = buffer.readVarInt() const capabilites = selectCapabilites(capabilityBitMask) - let major - let minor - versions.sort((a, b) => Number(b.major + '.' + b.minor) - Number(a.major + '.' + a.minor)) + let major = 0 + let minor = 0 + versions.sort((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 (let i = 0; i < versions.length; i++) { const version = versions[i] if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) { diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index 5e5f23423..9ad9846c1 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -87,9 +87,15 @@ function handshakeNegotiationV2 (channel, buffer, log) { const capabilityBitMask = buffer.readVarInt() const capabilites = selectCapabilites(capabilityBitMask) - let major - let minor - versions.sort((a, b) => Number(b.major + '.' + b.minor) - Number(a.major + '.' + a.minor)) + let major = 0 + let minor = 0 + versions.sort((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 (let i = 0; i < versions.length; i++) { const version = versions[i] if (AVAILABLE_BOLT_PROTOCOLS.includes(Number(version.major + '.' + version.minor))) {