Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically generate service types from the IANA registry #6

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.DS_Store
.vscode
/.build
/Downloads
/Packages
/*.xcodeproj
xcuserdata/
Expand Down
92 changes: 92 additions & 0 deletions Scripts/generate-service-types
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3

from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Iterable

import argparse
import csv
import re
import itertools
import urllib.request

ROOT_DIR = Path(__file__).resolve().parent.parent
DOWNLOADS_DIR = ROOT_DIR / 'Downloads'
DNSSERVICEDISCOVERY_SOURCES_DIR = ROOT_DIR / 'Sources' / 'DNSServiceDiscovery'

CACHED_REGISTRY_PATH = DOWNLOADS_DIR / 'service-registry.csv'

@dataclass
class ServiceType:
name: str
port: str
protocol: str
description: str
assignee: str

def camel_case(kebab_case: str) -> str:
segments = kebab_case.split('-')
return ''.join([segments[0]] + [s.capitalize() for s in segments[1:]])

def fetch_service_types(registry_csv_url: str) -> Iterable[ServiceType]:
if not CACHED_REGISTRY_PATH.exists():
CACHED_REGISTRY_PATH.parent.mkdir(parents=True, exist_ok=True)
with urllib.request.urlopen(registry_csv_url) as u:
with open(CACHED_REGISTRY_PATH, 'wb') as f:
f.write(u.read())

with open(CACHED_REGISTRY_PATH, 'r') as f:
used = set()
reader = csv.reader(f)
for name, port, protocol, description, assignee, *_ in itertools.islice(reader, 1, None):
if re.fullmatch(r'[a-zA-Z][a-zA-Z0-9\-_]+', name) and protocol:
key = f'{name}-{protocol}'
if key not in used:
used.add(key)
yield ServiceType(
name=name,
port=port,
protocol=protocol,
description=description,
assignee=assignee,
)

def swift_service_type_constant(service_type: ServiceType) -> str:
return f'public static let `{camel_case(f"{service_type.name}-{service_type.protocol}")}`: Self = "_{service_type.name}._{service_type.protocol}"'

def swift_service_types_extension(registry_csv_url: str, service_types: Iterable[ServiceType]) -> str:
date = datetime.now().strftime('%Y-%m-%d')
return '\n'.join([
f'/// Auto-generated from {registry_csv_url}',
f'/// on {date}. Please do not edit this file, edit `Scripts/generate-service-types` instead!',
'extension DNSServiceType {',
*(line for s in service_types for line in [
f" /// {' '.join(s.description.splitlines())} {s.assignee}",
f' {swift_service_type_constant(s)}',
]),
'}',
])

def main():
parser = argparse.ArgumentParser(description='Generates Swift definitions of known DNS service types from the IANA registry.')
parser.add_argument('-r', '--registry-csv-url', default='https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.csv')
parser.add_argument('-o', '--output', type=Path, default=DNSSERVICEDISCOVERY_SOURCES_DIR / 'DNSServiceType+Generated.swift')

args = parser.parse_args()

print('==> Fetching service types...')
service_types = fetch_service_types(args.registry_csv_url)

print('==> Generating Swift extension...')
extension = swift_service_types_extension(
registry_csv_url=args.registry_csv_url,
service_types=service_types
)

print(f'==> Writing {args.output}')
with open(args.output, 'w') as f:
f.write(extension)

if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion Snippets/BrowseSSHServers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Dispatch
let sd = DNSServiceDiscovery()

print("Browsing for SSH servers...")
sd.lookup(DNSServiceQuery(type: .ssh)) { instances in
sd.lookup(DNSServiceQuery(type: .sshTcp)) { instances in
for instance in try! instances.get() {
print(instance)
}
Expand Down
2 changes: 1 addition & 1 deletion Snippets/ResolveAirPlayServers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Dispatch
let sd = DNSServiceDiscovery()

print("Resolving all AirPlay servers...")
sd.lookup(DNSServiceQuery(type: .airplay)) {
sd.lookup(DNSServiceQuery(type: .airplayTcp)) {
let instances = try! $0.get()
for instance in instances {
sd.lookup(DNSServiceQuery(instance)) {
Expand Down
2 changes: 1 addition & 1 deletion Snippets/SubscribeToAirPlayServers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Dispatch
let sd = DNSServiceDiscovery()

print("Subscribing to AirPlay servers...")
let token = sd.subscribe(to: DNSServiceQuery(type: .airplay)) { instances in
let token = sd.subscribe(to: DNSServiceQuery(type: .airplayTcp)) { instances in
print(String(repeating: "-", count: 80))
for instance in try! instances.get() {
print(instance)
Expand Down
Loading