# dns-sd-lookup

[![npm version](http://img.shields.io/npm/v/@toryt/dns-sd-lookup.svg?style=flat)](https://npmjs.org/package/@toryt/dns-sd-lookup 'View this project on npm')
![license](https://img.shields.io/npm/l/@toryt/dns-sd-lookup)
![node-lts](https://img.shields.io/node/v-lts/@toryt/dns-sd-lookup)
![npm](https://img.shields.io/npm/dt/@toryt/dns-sd-lookup)
![dependencies](https://img.shields.io/david/Toryt/dns-sd-lookup)
![development dependencies](https://img.shields.io/david/dev/Toryt/dns-sd-lookup)
![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/@toryt/dns-sd-lookup)
![Bitbucket Pipelines](https://img.shields.io/bitbucket/pipelines/toryt/dns-sd-lookup)
![Codecov](https://img.shields.io/codecov/c/bitbucket/toryt/dns-sd-lookup)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/@toryt/dns-sd-lookup)
[![Bitbucket open issues](https://img.shields.io/bitbucket/issues/toryt/dns-sd-lookup)](https://bitbucket.org/toryt/dns-sd-lookup/issues)
[![Bitbucket open pull requests](https://img.shields.io/bitbucket/pr/toryt/dns-sd-lookup)](https://bitbucket.org/toryt/dns-sd-lookup/pull-requests/)
![npm collaborators](https://img.shields.io/npm/collaborators/@toryt/dns-sd-lookup)
![contributors](https://img.shields.io/github/contributors/Toryt/dns-sd-lookup)
![language count](https://img.shields.io/github/languages/count/toryt/dns-sd-lookup)
![top language](https://img.shields.io/github/languages/top/Toryt/dns-sd-lookup)
![commit activity](https://img.shields.io/github/commit-activity/y/Toryt/dns-sd-lookup)
![last commit](https://img.shields.io/github/last-commit/Toryt/dns-sd-lookup)

Node library that looks up service instance definitions for a service type defined with [DNS-SD] ([RFC 6763]).

**There is no support for Multicast DNS in this library.** This library only does look-ups in regular DNS. The
functionality is comparable to `dns-sd -B` and `dns-sd -L` (see [dns-sd]).

## Install

    > npm install --save @toryt/dns-sd-lookup

## Versions

| Version       | Build Status                                                                                                               | Coverage                                                                                                                              |
| :------------ | :------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ |
| `master`      | ![Bitbucket Pipelines](https://img.shields.io/bitbucket/pipelines/toryt/dns-sd-lookup)                                     | [![codecov](https://codecov.io/bb/toryt/dns-sd-lookup/branch/master/graph/badge.svg)](https://codecov.io/bb/toryt/dns-sd-lookup)      |
| `release/3/0` | ![Bitbucket Pipelines](https://img.shields.io/bitbucket/pipelines/toryt/dns-sd-lookup/release/3/0)                         | [![codecov](https://codecov.io/bb/Toryt/dns-sd-lookup/branch/release/3/0/graph/badge.svg)](https://codecov.io/bb/Toryt/dns-sd-lookup) |
| `3.0.2`       | ![Bitbucket Pipelines](https://img.shields.io/bitbucket/pipelines/toryt/dns-sd-lookup/v3.0.2)                              | [![codecov](https://codecov.io/bb/Toryt/dns-sd-lookup/branch/v3.0.2/graph/badge.svg)](https://codecov.io/bb/Toryt/dns-sd-lookup)      |
| `3.0.1`       | ![Bitbucket Pipelines](https://img.shields.io/bitbucket/pipelines/toryt/dns-sd-lookup/v3.0.1)                              | [![codecov](https://codecov.io/bb/Toryt/dns-sd-lookup/branch/v3.0.1/graph/badge.svg)](https://codecov.io/bb/Toryt/dns-sd-lookup)      |
| `3.0.0`       | ![Bitbucket Pipelines](https://img.shields.io/bitbucket/pipelines/toryt/dns-sd-lookup/v3.0.0)                              | [![codecov](https://codecov.io/bb/Toryt/dns-sd-lookup/branch/v3.0.0/graph/badge.svg)](https://codecov.io/bb/Toryt/dns-sd-lookup)      |
| `release/2/0` | ![Bitbucket Pipelines](https://img.shields.io/bitbucket/pipelines/toryt/dns-sd-lookup/release/2/0)                         | [![codecov](https://codecov.io/bb/Toryt/dns-sd-lookup/branch/release/2/0/graph/badge.svg)](https://codecov.io/bb/Toryt/dns-sd-lookup) |
| `2.0.4`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v2.0.4)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v2.0.4/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `2.0.3`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v2.0.3)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v2.0.3/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `2.0.2`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v2.0.2)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v2.0.2/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `2.0.1`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v2.0.1)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v2.0.1/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `2.0.0`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v2.0.0)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v2.0.0/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.15`      | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.15)](https://travis-ci.org/Toryt/dns-sd-lookup) | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.15/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)     |
| `1.1.14`      | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.14)](https://travis-ci.org/Toryt/dns-sd-lookup) | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.14/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)     |
| `1.1.13`      | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.13)](https://travis-ci.org/Toryt/dns-sd-lookup) | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.13/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)     |
| `1.1.12`      | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.12)](https://travis-ci.org/Toryt/dns-sd-lookup) | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.12/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)     |
| `1.1.11`      | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.11)](https://travis-ci.org/Toryt/dns-sd-lookup) | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.11/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)     |
| `1.1.10`      | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.10)](https://travis-ci.org/Toryt/dns-sd-lookup) | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.10/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)     |
| `1.1.9`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.9)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.9/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.8`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.8)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.8/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.7`       | _skipped_                                                                                                                  |                                                                                                                                       |
| `1.1.6`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.6)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.6/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.5`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.5)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.5/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.4`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.4)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.4/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.3`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.3)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.3/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.3`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.2)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.2/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.1`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.1)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.1/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.1.0`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.1.0)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.1.0/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.0.2`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.0.2)](https://travis-ci.org/Toryt/dns-sd-lookup)  | [![codecov](https://codecov.io/gh/Toryt/dns-sd-lookup/branch/v1.0.2/graph/badge.svg)](https://codecov.io/gh/Toryt/dns-sd-lookup)      |
| `1.0.1`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.0.1)](https://travis-ci.org/Toryt/dns-sd-lookup)  |                                                                                                                                       |
| `1.0.0`       | [![Build Status](https://travis-ci.org/Toryt/dns-sd-lookup.svg?branch=v1.0.0)](https://travis-ci.org/Toryt/dns-sd-lookup)  |                                                                                                                                       |

## Where to find

### Repo, CI, issues, pull requests

This project is maintained in [Bitbucket](https://bitbucket.org/toryt/dns-sd-lookup) (repo, CI, issues, pull requests,
…).

Branches are copied automatically to [Github](https://github.com/Toryt/dns-sd-lookup) by CI. This is done as backup, and
because open source projects are more easily found there. Issues and pull requests there will not be reviewed.

### npm

[@toryt/dns-sd-lookup](https://www.npmjs.com/package/@toryt/dns-sd-lookup)

## API

### `ServiceInstance`

All lookup methods return `ServiceInstance` objects. These represent an [RFC 6763] _Service Instance_ description.

Instances have the following properties:

- `type`: string; the full [RFC 6763] _Service Type_ name, including the optional [RFC 6763] _Service Subtype_
- `instance`: string; the full [RFC 6763] _Service Instance_ name
- `host`: string; the FQDN of the host that offers the _Service Instance_ (from the DNS `SRV` resource record)
- `port`: natural Number; the TCP port at which the `host` offers the _Service Instance_ (from the DNS `SRV` resource
  record)
- `priority`: natural Number, that says with which priority the user should use this instance to fulfill `type` (lower
  is higher priority - from the DNS `SRV` resource record)
- `weight`: natural Number, that says how often the user should use this instance to fulfill `type`, when choosing
  between instances with the same `priority` (from the DNS `SRV` resource record)
- `details`: Object, that carries extra details about the _Service Instance_ this represents

The details are the key-value pairs expressed in the DNS `TXT` record. All property names are lower case. [RFC 6763]
boolean attributes (i.e., DNS `TXT` resource record strings that do not contain a `=`-character) are represented by a
property with the attribute name as property name, and the value `true`. Otherwise, `details` property values of are
always strings, and never `null` or `undefined`, but they might be the empty string. In general, a property value of a
`ServiceInstance.details` object can also be `false`, but an instance returned by one of the lookup methods of this
library will never return that.

According to [RFC 6763], the `details` should at least have a property `txtvers`, with a string value that represents a
natural number.

    const ServiceInstance = require('@toryt/dns-sd-lookup).ServiceInstance

    const instance = new ServiceInstance({
      type: 'sub type._sub._a-service-type._tcp.dns-sd-lookup.toryt.org',
      instance: 'A Service Instance._a-service-type._tcp.dns-sd-lookup.toryt.org',
      host: 'service-host.dns-sd-lookup.toryt.org',
      port: 443,
      priority: 0,
      weight: 0,
      details: {
        at: JSON.stringify(new Date(2017, 9, 17, 0, 33, 14.535)),
        path: '/a/path',
        '%boolean#true]': true,
        'boolean@false~': false,
        txtvers: '23'
      }
    })

    console.log(instance)
    console.log('%j', instance)

### `validate`

A collection of string validation methods, related to [RFC 6763].

#### `isSubtypeOrInstanceName`

The given string is a valid DNS-SD subtype or short instance name.

This means it is a _DNS label_, with dots and backslashes escaped. A DNS label consists of at least 1, and not more then
63 characters ('octets'). Any character (octet) is allowed in a DNS label. (This in contrast to a _host name_, the parts
of an _internet host name_, _DNS domain_ or _DNS subdomain_, for which the allowed characters are limited. E.g., they
cannot contain '\_' or spaces, control characters, etc.).

This function does not allow gratuitous escapes, i.e., a backslash must be followed by a dot or another backslash.

    const isSubtypeOrInstanceName = require('@toryt/dns-sd-lookup).isSubtypeOrInstanceName

    console.assert(isSubtypeOrInstanceName('any ∆é^# ï € / octet! is all0wed'))
    console.assert(isSubtypeOrInstanceName('dots\\.must\\.be\\.escaped'))
    console.assert(isSubtypeOrInstanceName('backslash\\\\must\\\\be\\\\escaped'))

    console.assert(!isSubtypeOrInstanceName('aLabelThatIsLongerThanIsAcceptableWhichIs63ACharactersLongLabels'))
    console.assert(!isSubtypeOrInstanceName('label.with.unescaped.dot'))
    console.assert(!isSubtypeOrInstanceName('label with \\gratuitous escape'))

#### `isBaseServiceType`

The given string represents a [RFC 6763] base _Service Type_, i.e., a _Service Type_ without a subtype.

    const isBaseServiceType = require('@toryt/dns-sd-lookup).isBaseServiceType

    console.assert(isBaseServiceType('_a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(isBaseServiceType('_a-service-type._udp.dns-sd-lookup.toryt.org'))
    console.assert(isBaseServiceType('_http._udp.dns-sd-lookup.toryt.org'))

    console.assert(!isBaseServiceType('sub type._sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_a-service-type-that-is-too-long._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_not-a-type.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_not-a-type._other.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_not-a-type._tcp.not_a_fqdn'))
    console.assert(!isBaseServiceType('_not a type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('not-a-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_not-a--type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_not_a_type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_not a type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_9not-a-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_not-a-type9._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_-not-a-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isBaseServiceType('_not-a-type-._tcp.dns-sd-lookup.toryt.org'))

#### `isBaseServiceType`

The given string represents a [RFC 6763] _Service Type_, i.e., a base _Service Type_, or a _service Type_ with a
subtype.

This function does not allow gratuitous escapes, i.e., a backslash must be followed by a dot or another backslash in the
subtype label.

    const isServiceType = require('@toryt/dns-sd-lookup).isServiceType

    console.assert(isServiceType('_a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(isServiceType('_sub-type._sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(isServiceType('sub type._sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(isServiceType('_a\\.complex\\\\sub\\.service._sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))

    console.assert(!isServiceType('sub type._not-a-type._other.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceType('_sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceType('._sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceType('unescaped.dot._sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceType('unescaped\\backslash._sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceType('ThisIsLongerThanTheMaximumLengthWhichIs63CharactersForAnDNSLabel._sub._a-service-type._tcp.dns-sd-lookup.toryt.org'))

#### `isServiceInstance`

The given string represents a [RFC 6763] _Service Instance_.

This function does not allow gratuitous escapes, i.e., a backslash must be followed by a dot or another backslash in the
instance name label.

    const isServiceInstance = require('@toryt/dns-sd-lookup).isServiceInstance

    console.assert(isServiceInstance('Instance Sérvice ∆._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(isServiceInstance('instances\\.with\\.escaped\\\\dots\\\\and\\.slashes._a-service-type._tcp.dns-sd-lookup.toryt.org'))

    console.assert(!isServiceInstance('instance._not-a-type._other.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceInstance('instance._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceInstance('_a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceInstance('._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceInstance('unescaped.dot._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceInstance('unescaped\\backslash._a-service-type._tcp.dns-sd-lookup.toryt.org'))
    console.assert(!isServiceInstance('anInstanceThatIsLongerThanIsAcceptableWhichIs63ACharactersLabels._a-service-type._tcp.dns-sd-lookup.toryt.org'))

#### `validate`

The validate-functions are gathered in the namespace `validate`.

    const validate = require('@toryt/dns-sd-lookup).validate

    console.assert(validate.isBaseServiceType === require('@toryt/dns-sd-lookup).isBaseServiceType)
    console.assert(validate.isServiceType === require('@toryt/dns-sd-lookup).isServiceType)
    console.assert(validate.isServiceInstance === require('@toryt/dns-sd-lookup).isServiceInstance)

### `extract`

#### `extract.subtype`

Extract the subtype from a [RFC 6763] _Service Type_. If there is no subtype, the result is `undefined`.

      const extract = require('@toryt/dns-sd-lookup).extract

      console.assert(extract.subtype('_a-service-type._tcp.dns-sd-lookup.toryt.org') === undefined)
      console.assert(extract.subtype('_a-sub-service._sub._a-service-type._tcp.dns-sd-lookup.toryt.org') === '_a-sub-service')

#### `extract.type`

Extract the (base) type from a [RFC 6763] _Service Type_ or _Service Instance_.

      const extract = require('@toryt/dns-sd-lookup).extract

      console.assert(extract.type('_a-service-type._tcp.dns-sd-lookup.toryt.org') === 'a-service-type')
      console.assert(extract.type('_a-sub-service._sub._a-service-type._tcp.dns-sd-lookup.toryt.org') === 'a-service-type')
      console.assert(extract.type('Service Instance._sub._a-service-type._tcp.dns-sd-lookup.toryt.org') === 'a-service-type')

#### `extract.instance`

Extract the instance from a [RFC 6763] _Service Instance_.

      const extract = require('@toryt/dns-sd-lookup).extract

      console.assert(extract.instance('Service Instance._a-service-type._tcp.dns-sd-lookup.toryt.org') === 'Service Instance')

#### `extract.protocol`

Extract the protocol from a [RFC 6763] _Service Type_ or _Service Instance_.

      const extract = require('@toryt/dns-sd-lookup).extract

      console.assert(extract.protocol('_a-service-type._tcp.dns-sd-lookup.toryt.org') === 'tcp')
      console.assert(extract.protocol('_a-sub-service._sub._a-service-type._udp.dns-sd-lookup.toryt.org') === 'udp')
      console.assert(extract.protocol('Service Instance._a-service-type._tcp.dns-sd-lookup.toryt.org') === 'tcp')

#### `extract.domain`

Extract the domain from a [RFC 6763] _Service Type_ or _Service Instance_.

      const extract = require('@toryt/dns-sd-lookup).extract

      console.assert(extract.domain('_a-service-type._tcp.dns-sd-lookup.toryt.org') === 'dns-sd-lookup.toryt.org')
      console.assert(extract.domain('_a-sub-service._sub._a-service-type._udp.dns-sd-lookup.toryt.org') === 'dns-sd-lookup.toryt.org')
      console.assert(extract.domain('Service Instance._a-service-type._tcp.dns-sd-lookup.toryt.org') === 'dns-sd-lookup.toryt.org')

### `extendWithTxtStr`

Extend a given `obj` with a property based on a given DNS `TXT` resource record string that represents a [RFC 6763]
_Service Instance_ attribute.

Existing properties are never overwritten. The attribute name is converted to lower case before it is used as property
name. DNS `TXT` resource record strings that do not represents a valid [RFC 6763] _Service Instance_ attribute leave the
`obj` unchanged. A DNS `TXT` resource record string that represents a [RFC 6763] _Service Instance_ boolean attribute,
i.e., that does not contain a `=` character, result in a JavaScript property on `obj` with value `true`.

The property values added by this method to `obj` are always either `true` or a string. The property value added might
be the empty string. const extendWithTxtStr = require('@toryt/dns-sd-lookup).extendWithTxtStr

    const obj = {
      existing: 'existing property'
    }
    extendWithTxtStr(obj, 'newProperty=new property value')
    console.assert(obj.newproperty === 'new property value')
    extendWithTxtStr(obj, 'existing=override')
    console.assert(obj.existing === 'existing property')
    extendWithTxtStr(obj, '=this is not a valid attribute')
    console.assert(obj[''] === undefined)
    extendWithTxtStr(obj, 'empty string attribute=')
    console.assert(obj['empty string attribute'] === '')
    extendWithTxtStr(obj, 'boolean attribute')
    console.assert(obj['boolean attribute'] === true)

    console.log('%j', obj)

prints out

    {
      "existing": "existing property",
      "newproperty": "new property value",
      "empty string attribute": "",
      "boolean attribute": true
    }

### `lookupInstance`

Lookup the definition a [RFC 6763] _Service Instance_ in DNS and resolve to a `ServiceInstance` that represents it.

The function returns a Promise. If not exactly 1 DNS `SRV` and exactly 1 DNS `TXT` resource record is found in DNS for
the given _Service Instance_, the Promise is betrayed. The `details` property holds an object that contains all valid
attributes found in the DNS `TXT` resource record, according to `extendWithTxtStr`.

    const lookupInstance = require('../index').lookupInstance

    const serviceInstance = await lookupInstance('instance_1._t1i-no-sub._tcp.dns-sd-lookup.toryt.org')
    console.log('%j', serviceInstance)

prints out

    {
      "type": "_t1i-no-sub._tcp.dns-sd-lookup.toryt.org",
      "instance": "instance 1._t1i-no-sub._tcp.dns-sd-lookup.toryt.org",
      "host": "host-of-instance-1.dns-sd-lookup.toryt.org",
      "port": 4141,
      "priority": 42,
      "weight": 43,
      "details": {
        "adetail": "This is a detail 1",
        "at": "2017-09-30T13:25:49Z",
        "txtvers":"44"
      }
    }

### `discover`

Lookup all instances for the given [RFC 6763] _Service Type_ in DNS and resolve to an Array of `ServiceInstance` objects
that represent them. Optionally, you can provide a `filter` function that filters out instances based on the _Service
Instance_ name. With that, users can specify _Service Instance_ they do not want, because they know they are not in good
health, or because they know by name that they are not interesting.

`discover.notOneOf` is a helper function that creates a `filter` function out of an Array of _Service Instance_ names.

The function does a lookup for the DNS `PTR` resource records for the _Service Type_, and does `lookupInstance` for all
instances found, that pass the `filter`.

The function returns a Promise. If there are not exactly 1 DNS `SRV` and exactly 1 DNS `TXT` resource record in DNS for
all found instances that pass the `filter`, the Promise is betrayed. If there is no `PTR` resource record for the
_Service Type_, or all instances are filtered out, the Promise returns the empty Array.

    const discover = require('../index').discover

    const serviceType = '_t8i-n-inst._tcp.dns-sd-lookup.toryt.org'
    let deaths = [
      'instance_8c',
      'instance_8e',
      'instance_8g',
      'instance_8h',
      'instance_8i',
      'instance_8k',
      'instance_8l'
    ]
    deaths = deaths.map(d => `${d}.${serviceType}`)

    const serviceInstances = await discover(serviceType, discover.notOneOf(deaths))
    console.log('%j', serviceInstances)

prints out

    [
      {
        "type": "_t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "instance": "instance 8f._t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "host": "host-of-instance-8f.dns-sd-lookup.toryt.org",
        "port": 9898,
        "priority": 200,
        "weight": 20,
        "details": {"adetail": "This is a detail 99", "at": "2017-09-30T13:25:49Z", "txtvers": "100"}
      },
      {
        "type": "_t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "instance": "instance 8a._t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "host": "host-of-instance-8a.dns-sd-lookup.toryt.org",
        "port": 7373,
        "priority": 50,
        "weight": 75,
        "details": {"adetail": "This is a detail 76", "at": "2017-09-30T13:25:49Z", "txtvers": "77"}
      },
      {
        "type": "_t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "instance": "instance 8d._t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "host": "host-of-instance-8d.dns-sd-lookup.toryt.org",
        "port": 8888,
        "priority": 150,
        "weight": 7,
        "details": {"adetail": "This is a detail 91", "at": "2017-09-30T13:25:49Z", "txtvers": "92"}
      },
      {
        "type": "_t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "instance": "instance 8j._t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "host": "host-of-instance-8j.dns-sd-lookup.toryt.org",
        "port": 2121,
        "priority": 300,
        "weight": 0,
        "details": {"adetail": "This is a detail 112", "at": "2017-09-30T13:25:49Z", "txtvers": "113"}
      },
      {
        "type": "_t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "instance": "instance 8b._t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
        "host": "host-of-instance-8b.dns-sd-lookup.toryt.org",
        "port": 7878,
        "priority": 100,
        "weight": 80,
        "details": {"adetail": "This is a detail 81", "at": "2017-09-30T13:25:49Z", "txtvers": "82"}
      }
    ]

The order of the instances is unspecified.

### `selectInstance`

Lookup all instances for the given [RFC 6763] _Service Type_ in DNS and resolve to the `ServiceInstance` the user should
use. Optionally, you can provide a `filter` function that filters out instances based on the _Service Instance_ name.
With that, users can specify _Service Instance_ they do not want, because they know they are not in good health, or
because they know by name that they are not interesting.

`selectInstance.notOneOf` is a helper function that creates a `filter` function out of an Array of _Service Instance_
names.

The function uses `discover`, and the selects the appropriate instance from the resulting Array that have passed the
`filter`, according to the rules of [RFC 2782]. The instance in the list with the lowest `priority` value is chosen. If
there is more then 1 instance with the same lowest `priority` value, one is choose random from that set, according to
the chance distribution given by the `weight` property values of the instance in the set.

The function returns a Promise. If there are not exactly 1 DNS `SRV` and exactly 1 DNS `TXT` resource record in DNS for
all found instances that pass the `filter`, the Promise is betrayed. If there is no `PTR` resource record for the
_Service Type_, or all instances are filtered out, the Promise returns `null`.

    const selectInstance = require('../index').selectInstance

    const serviceType = '_t8i-n-inst._tcp.dns-sd-lookup.toryt.org'
    let deaths = ['instance_8a', 'instance_8b', 'instance_8c', 'instance_8d']
    deaths = deaths.map(d => `${d}.${serviceType}`)

    const serviceInstance = await selectInstance(serviceType, selectInstance.notOneOf(deaths))
    console.log('%j', serviceInstance)

prints out, e.g.,

    {
      "type": "_t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
      "instance": "instance 8f._t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
      "host": "host-of-instance-8f.dns-sd-lookup.toryt.org",
      "port": 9898,
      "priority": 200,
      "weight": 20,
      "details": {"adetail": "This is a detail 99", "at": "2017-09-30T13:25:49Z", "txtvers": "100"}
    }

In the example, all instances _e_ through _i_ have the same `priority`, so in another run, you might get another answer
from that set, e.g.,

    {
      "type": "_t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
      "instance": "instance 8i._t8i-n-inst._tcp.dns-sd-lookup.toryt.org",
      "host": "host-of-instance-8i.dns-sd-lookup.toryt.org",
      "port": 1818,
      "priority": 200,
      "weight": 20,
      "details": {"adetail": "This is a detail 109", "at": "2017-09-30T13:25:49Z", "txtvers": "110"}
    }

## Side information

While building this library, I had to burrow through some confusion. Here are some notes:

- [Difference between DNS labels and full names, and internet host names]
- [How to define multi-string TXT resource records]

## Style

[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)

This code uses [Standard] coding style.

Coverage with [Istanbul] and [Codecov].

## License

Released under the [MIT License]

### MIT License

Copyright (c) 2017-2020 Jan Dockx

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

## Credits

`dns-sd-lookup` builds on the work of many people through [F/OSS]. See the [credits].

[dns-sd]: http://www.dns-sd.org
[rfc 6763]: https://www.ietf.org/rfc/rfc6763.txt
[dns-sd]: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/dns-sd.1.html
[rfc 2782]: https://www.ietf.org/rfc/rfc2782.txt
[difference between dns labels and full names, and internet host names]: ./On_dns_full_and_domain_and_host_names.md
[how to define multi-string txt resource records]: ./HowtoDefineMultiStringTXTRecords.md
[standard]: https://standardjs.com
[istanbul]: https://istanbul.js.org
[codecov]: https://codecov.io/bb/toryt/dns-sd-lookup
[mit license]: ./LICENSE
[f/oss]: https://opensource.org
[credits]: ./CREDITS.md
