function Server (options) {
  if (options) {
    if (typeof (options) !== 'object') { throw new TypeError('options (object) required') }
    if (typeof (options.log) !== 'object') { throw new TypeError('options.log must be an object') }

    if (options.certificate || options.key) {
      if (!(options.certificate && options.key) ||
          (typeof (options.certificate) !== 'string' &&
          !Buffer.isBuffer(options.certificate)) ||
          (typeof (options.key) !== 'string' &&
          !Buffer.isBuffer(options.key))) {
        throw new TypeError('options.certificate and options.key ' +
                            '(string or buffer) are both required for TLS')
      }
    }
  } else {
    options = {}
  }
  const self = this

  EventEmitter.call(this, options)

  this._chain = []
  this.log = options.log
  const log = this.log

  function setupConnection (c) {
    assert.ok(c)

    if (c.type === 'unix') {
      c.remoteAddress = self.server.path
      c.remotePort = c.fd
    } else if (c.socket) {
      // TLS
      c.remoteAddress = c.socket.remoteAddress
      c.remotePort = c.socket.remotePort
    }

    const rdn = new RDN({ cn: 'anonymous' })

    c.ldap = {
      id: c.remoteAddress + ':' + c.remotePort,
      config: options,
      _bindDN: new DN({ rdns: [rdn] })
    }
    c.addListener('timeout', function () {
      log.trace('%s timed out', c.ldap.id)
      c.destroy()
    })
    c.addListener('end', function () {
      log.trace('%s shutdown', c.ldap.id)
    })
    c.addListener('error', function (err) {
      log.warn('%s unexpected connection error', c.ldap.id, err)
      self.emit('clientError', err)
      c.destroy()
    })
    c.addListener('close', function (closeError) {
      log.trace('%s close; had_err=%j', c.ldap.id, closeError)
      c.end()
    })

    c.ldap.__defineGetter__('bindDN', function () {
      return c.ldap._bindDN
    })
    c.ldap.__defineSetter__('bindDN', function (val) {
      if (Object.prototype.toString.call(val) !== '[object LdapDn]') {
        throw new TypeError('DN required')
      }

      c.ldap._bindDN = val
      return val
    })
    return c
  }

  self.newConnection = function (conn) {
    // TODO: make `newConnection` available on the `Server` prototype
    // https://github.com/ldapjs/node-ldapjs/pull/727/files#r636572294
    setupConnection(conn)
    log.trace('new connection from %s', conn.ldap.id)

    conn.parser = new Parser({
      log: options.log
    })
    conn.parser.on('message', function (req) {
      // TODO: this is mutating the `@ldapjs/message` objects.
      // We should avoid doing that. ~ jsumners 2023-02-16
      req.connection = conn
      req.logId = conn.ldap.id + '::' + req.messageId
      req.startTime = new Date().getTime()

      log.debug('%s: message received: req=%j', conn.ldap.id, req.pojo)

      const res = getResponse(req)
      if (!res) {
        log.warn('Unimplemented server method: %s', req.type)
        conn.destroy()
        return false
      }

      // parse string DNs for routing/etc
      try {
        switch (req.protocolOp) {
          case Protocol.operations.LDAP_REQ_BIND: {
            req.name = DN.fromString(req.name)
            break
          }

          case Protocol.operations.LDAP_REQ_ADD:
          case Protocol.operations.LDAP_REQ_COMPARE:
          case Protocol.operations.LDAP_REQ_DELETE: {
            if (typeof req.entry === 'string') {
              req.entry = DN.fromString(req.entry)
            } else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
              throw Error('invalid entry object for operation')
            }
            break
          }

          case Protocol.operations.LDAP_REQ_MODIFY: {
            req.object = DN.fromString(req.object)
            break
          }

          case Protocol.operations.LDAP_REQ_MODRDN: {
            if (typeof req.entry === 'string') {
              req.entry = DN.fromString(req.entry)
            } else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
              throw Error('invalid entry object for operation')
            }
            // TODO: handle newRdn/Superior
            break
          }

          case Protocol.operations.LDAP_REQ_SEARCH: {
            break
          }

          default: {
            break
          }
        }
      } catch (e) {
        return res.end(errors.LDAP_INVALID_DN_SYNTAX)
      }

      res.connection = conn
      res.logId = req.logId
      res.requestDN = req.dn

      const chain = self._getHandlerChain(req, res)

      let i = 0
      return (function messageIIFE (err) {
        function sendError (sendErr) {
          res.status = sendErr.code || errors.LDAP_OPERATIONS_ERROR
          res.matchedDN = req.suffix ? req.suffix.toString() : ''
          res.errorMessage = sendErr.message || ''
          return res.end()
        }

        function after () {
          if (!self._postChain || !self._postChain.length) { return }

          function next () {} // stub out next for the post chain

          self._postChain.forEach(function (cb) {
            cb.call(self, req, res, next)
          })
        }

        if (err) {
          log.trace('%s sending error: %s', req.logId, err.stack || err)
          self.emit('clientError', err)
          sendError(err)
          return after()
        }

        try {
          const next = messageIIFE
          if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) }

          if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND && res.status === 0) {
            // 0 length == anonymous bind
            if (req.dn.length === 0 && req.credentials === '') {
              conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
            } else {
              conn.ldap.bindDN = DN.fromString(req.dn)
            }
          }

          // unbind clear bindDN for safety
          // conn should terminate on unbind (RFC4511 4.3)
          if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND && res.status === 0) {
            conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
          }

          return after()
        } catch (e) {
          if (!e.stack) { e.stack = e.toString() }
          log.error('%s uncaught exception: %s', req.logId, e.stack)
          return sendError(new errors.OperationsError(e.message))
        }
      }())
    })

    conn.parser.on('error', function (err, message) {
      self.emit('error', new VError(err, 'Parser error for %s', conn.ldap.id))

      if (!message) { return conn.destroy() }

      const res = getResponse(message)
      if (!res) { return conn.destroy() }

      res.status = 0x02 // protocol error
      res.errorMessage = err.toString()
      return conn.end(res.toBer())
    })

    conn.on('data', function (data) {
      log.trace('data on %s: %s', conn.ldap.id, util.inspect(data))

      conn.parser.write(data)
    })
  } // end newConnection

  this.routes = {}
  if ((options.cert || options.certificate) && options.key) {
    options.cert = options.cert || options.certificate
    this.server = tls.createServer(options, options.connectionRouter ? options.connectionRouter : self.newConnection)
  } else {
    this.server = net.createServer(options.connectionRouter ? options.connectionRouter : self.newConnection)
  }
  this.server.log = options.log
  this.server.ldap = {
    config: options
  }
  this.server.on('close', function () {
    self.emit('close')
  })
  this.server.on('error', function (err) {
    self.emit('error', err)
  })
}
Constructs a new server that you can call .listen() on, in the various
forms node supports.  You need to first assign some handlers to the various
LDAP operations however.

The options object currently only takes a certificate/private key, and a
bunyan logger handle.

This object exposes the following events:
 - 'error'
 - 'close'

@param {Object} options (optional) parameterization object.
@throws {TypeError} on bad input.