'use strict'; const Packet = require('../packets/packet'); const ClientConstants = require('../constants/client.js'); // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake class Handshake { constructor(args) { this.protocolVersion = args.protocolVersion; this.serverVersion = args.serverVersion; this.capabilityFlags = args.capabilityFlags; this.connectionId = args.connectionId; this.authPluginData1 = args.authPluginData1; this.authPluginData2 = args.authPluginData2; this.characterSet = args.characterSet; this.statusFlags = args.statusFlags; this.autPluginName = args.autPluginName; } setScrambleData(cb) { require('crypto').randomBytes(20, (err, data) => { if (err) { cb(err); return; } this.authPluginData1 = data.slice(0, 8); this.authPluginData2 = data.slice(8, 20); cb(); }); } toPacket(sequenceId) { const length = 68 + Buffer.byteLength(this.serverVersion, 'utf8'); const buffer = Buffer.alloc(length + 4, 0); // zero fill, 10 bytes filler later needs to contain zeros const packet = new Packet(sequenceId, buffer, 0, length + 4); packet.offset = 4; packet.writeInt8(this.protocolVersion); packet.writeString(this.serverVersion, 'cesu8'); packet.writeInt8(0); packet.writeInt32(this.connectionId); packet.writeBuffer(this.authPluginData1); packet.writeInt8(0); const capabilityFlagsBuffer = Buffer.allocUnsafe(4); capabilityFlagsBuffer.writeUInt32LE(this.capabilityFlags, 0); packet.writeBuffer(capabilityFlagsBuffer.slice(0, 2)); packet.writeInt8(this.characterSet); packet.writeInt16(this.statusFlags); packet.writeBuffer(capabilityFlagsBuffer.slice(2, 4)); packet.writeInt8(21); // authPluginDataLength packet.skip(10); packet.writeBuffer(this.authPluginData2); packet.writeInt8(0); packet.writeString('mysql_native_password', 'latin1'); packet.writeInt8(0); return packet; } static fromPacket(packet) { const args = {}; args.protocolVersion = packet.readInt8(); args.serverVersion = packet.readNullTerminatedString('cesu8'); args.connectionId = packet.readInt32(); args.authPluginData1 = packet.readBuffer(8); packet.skip(1); const capabilityFlagsBuffer = Buffer.allocUnsafe(4); capabilityFlagsBuffer[0] = packet.readInt8(); capabilityFlagsBuffer[1] = packet.readInt8(); if (packet.haveMoreData()) { args.characterSet = packet.readInt8(); args.statusFlags = packet.readInt16(); // upper 2 bytes capabilityFlagsBuffer[2] = packet.readInt8(); capabilityFlagsBuffer[3] = packet.readInt8(); args.capabilityFlags = capabilityFlagsBuffer.readUInt32LE(0); if (args.capabilityFlags & ClientConstants.PLUGIN_AUTH) { args.authPluginDataLength = packet.readInt8(); } else { args.authPluginDataLength = 0; packet.skip(1); } packet.skip(10); } else { args.capabilityFlags = capabilityFlagsBuffer.readUInt16LE(0); } const isSecureConnection = args.capabilityFlags & ClientConstants.SECURE_CONNECTION; if (isSecureConnection) { const authPluginDataLength = args.authPluginDataLength; if (authPluginDataLength === 0) { // for Secure Password Authentication args.authPluginDataLength = 20; args.authPluginData2 = packet.readBuffer(12); packet.skip(1); } else { // length > 0 // for Custom Auth Plugin (PLUGIN_AUTH) const len = Math.max(13, authPluginDataLength - 8); args.authPluginData2 = packet.readBuffer(len); } } if (args.capabilityFlags & ClientConstants.PLUGIN_AUTH) { args.autPluginName = packet.readNullTerminatedString('ascii'); } return new Handshake(args); } } module.exports = Handshake;