schangxiang@126.com
2025-09-18 49a51c068d62084bc4c3e77c4be94a20de556c4a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"use strict";
 
const Sender = require('./sender').Sender;
 
const SQL_SERVER_BROWSER_PORT = 1434;
const TIMEOUT = 2 * 1000;
const RETRIES = 3; // There are three bytes at the start of the response, whose purpose is unknown.
 
const MYSTERY_HEADER_LENGTH = 3; // Most of the functionality has been determined from from jTDS's MSSqlServerInfo class.
 
class InstanceLookup {
  constructor() {} // Wrapper allows for stubbing Sender when unit testing instance-lookup.
 
 
  createSender(host, port, request) {
    return new Sender(host, port, request);
  }
 
  instanceLookup(options, callback) {
    const server = options.server;
 
    if (typeof server !== 'string') {
      throw new TypeError('Invalid arguments: "server" must be a string');
    }
 
    const instanceName = options.instanceName;
 
    if (typeof instanceName !== 'string') {
      throw new TypeError('Invalid arguments: "instanceName" must be a string');
    }
 
    const timeout = options.timeout === undefined ? TIMEOUT : options.timeout;
 
    if (typeof timeout !== 'number') {
      throw new TypeError('Invalid arguments: "timeout" must be a number');
    }
 
    const retries = options.retries === undefined ? RETRIES : options.retries;
 
    if (typeof retries !== 'number') {
      throw new TypeError('Invalid arguments: "retries" must be a number');
    }
 
    if (typeof callback !== 'function') {
      throw new TypeError('Invalid arguments: "callback" must be a function');
    }
 
    let sender,
        timer,
        retriesLeft = retries;
 
    const onTimeout = () => {
      sender.cancel();
      makeAttempt();
    };
 
    const makeAttempt = () => {
      if (retriesLeft > 0) {
        retriesLeft--;
        const request = Buffer.from([0x02]);
        sender = this.createSender(options.server, SQL_SERVER_BROWSER_PORT, request);
        sender.execute((err, message) => {
          clearTimeout(timer);
 
          if (err) {
            callback('Failed to lookup instance on ' + server + ' - ' + err.message);
            return;
          } else {
            message = message.toString('ascii', MYSTERY_HEADER_LENGTH);
            const port = this.parseBrowserResponse(message, instanceName);
 
            if (port) {
              callback(undefined, port);
            } else {
              callback('Port for ' + instanceName + ' not found in ' + options.server);
            }
          }
        });
        timer = setTimeout(onTimeout, timeout);
      } else {
        callback('Failed to get response from SQL Server Browser on ' + server);
      }
    };
 
    makeAttempt();
  }
 
  parseBrowserResponse(response, instanceName) {
    let getPort;
    const instances = response.split(';;');
 
    for (let i = 0, len = instances.length; i < len; i++) {
      const instance = instances[i];
      const parts = instance.split(';');
 
      for (let p = 0, partsLen = parts.length; p < partsLen; p += 2) {
        const name = parts[p];
        const value = parts[p + 1];
 
        if (name === 'tcp' && getPort) {
          const port = parseInt(value, 10);
          return port;
        }
 
        if (name === 'InstanceName') {
          if (value.toUpperCase() === instanceName.toUpperCase()) {
            getPort = true;
          } else {
            getPort = false;
          }
        }
      }
    }
  }
 
}
 
module.exports.InstanceLookup = InstanceLookup;