modules/tinydns/lib.nix

126 lines
5.0 KiB
Nix

{ lib }:
let
inherit (lib) concatStrings concatMapStringsSep concatStringsSep
getAttrFromPath getBin literalExample mapAttrsToList mkForce mkIf
mkChangedOptionModule mkEnableOption mkOption mod optional optionalString
remove singleton splitString types;
inherit (builtins) attrNames genList head isFunction isString length
removeAttrs replaceStrings stringLength substring tail;
_lib = rec {
# Convert a number base 10 to another number base k (k <= 16)
# l determins the 0 prefixed length of the output.
# Returns the output as a string
klconv = k: l: num:
let
snum = { base = k; d = num; c = 0; result = ""; };
replaceDigit = d: replaceStrings [ "10" "11" "12" "13" "14" "15" ]
[ "a" "b" "c" "d" "e" "f" ] d;
kconvDigit = { d, ... }@args:
args // { d = args.d / args.base;
result = replaceDigit (toString (d - ((d / args.base) * args.base))) + args.result;
};
kconv' = snum:
if snum.d == 0
then if stringLength snum.result < l
then (concatStrings (genList (_: "0") (l - stringLength snum.result))) + snum.result
else snum.result
else kconv' (kconvDigit snum);
in kconv' snum;
# Convert integer to octal code string of 16 or 8 bits
octconv16 = x: let
low = mod x 256;
high = (mod x 65536) / 256;
in "\\" + (substring 0 3 (klconv 8 3 high)) + "\\" + (substring 0 3 (klconv 8 3 low));
octconv8 = x: let s =(klconv 8 3 x); in "\\" + (substring 0 3 s);
# Convert seconds since Epoch to TAI64 into hex-coded string external format
epochToTAI64 = epoch: klconv 16 0 (4611686018427387904 + epoch);
# Quote special characters with octal replacements to suit tinydns-data
quoteDATA = s: replaceStrings [ " " "#" ":" ]
[ "\\040" "\\043" "\\072" ] s;
# Convert string into fixed-length string
# abcd -> \004abcd
fixedString = s: let
prefixShort = short: (octconv8 (stringLength short)) + (quoteDATA short);
fixedString' = s:
if stringLength s > 255
then (prefixShort (substring 0 255 s))+ (fixedString' (substring 255 (stringLength s) s))
else prefixShort s;
in
if stringLength s <= 65280 then fixedString' s
else abort "String payload too big, must be less than 65281 bytes";
# Split target at '.' into a length-prefixed string list suitable for tinydns-data
# abcd.ef. -> \004abcd\002ef\000
splitTarget = target:
concatStrings (map fixedString (splitString "." target));
# Parse DNS records defined in option records.
# Delivers var-args for the data* lambda type functions by supplying default
# values (i.e. "") for missing arguments.
parseRecord = f: l: if isFunction f
then if (length l) > 0
then parseRecord (f (head l)) (tail l)
else parseRecord (f "") []
else f;
# Capture the varargs parser and its varargs into a set containing all
# arguments as a list. Run the captured parser on the args when coercing the
# set to string.
captureParser = parser: { args = [];
__functor = self: arg: self // { args = self.args ++ [ arg ]; };
__toString = self: parser self.args;
};
# Make a DNS record by taking a prefabricated record and parsing the
# additional flags ttl, timestamp and location
dataFlags = record: ttl: ts: location:
"${record}"
+ ":${if ttl == null then "" else if isString ttl then ttl else toString ttl}"
+ ":${if ts == null then "" else if isString ts then ts else epochToTAI64 ts}"
+ ":${location}";
}; # _lib
# Each parser is expected to produce a string.
#
# captureParser collects the (variable amount of) arguments and the specific
# parser. It sets up the result such that, when it is coerced to a string, the
# captured parser is called with the captured arguments.
#
# parseRecord ensures that the specialized parser is called with enough
# arguments eventually. Left-out arguments are replaced with the empty string.
#
# Parser-building functions are collected in lib.
_parsers = with _lib; {
lib = _lib;
# Add the flags TTL, timestamp and location to a record
Flag = captureParser (parseRecord dataFlags);
# Make an AFSDB DNS record
AFSDB = captureParser (parseRecord (cell: server: dataFlags
(":${cell}:18:" + (octconv16 1) + (splitTarget server))));
# Make a SRV DNS record
SRV = captureParser (parseRecord (service: prio: w: port: target: dataFlags
(":${service}:33:" + (concatStrings (map octconv16 [ prio w port ])) + (splitTarget target))));
# Make a TXT DNS record
TXT = captureParser (parseRecord (service: txt: dataFlags
(":${service}:16:" + (fixedString txt))));
# Make a URI DNS record
URI = captureParser (parseRecord (service: prio: weight: pl: dataFlags
(":${service}:256:" + "${octconv16 prio}${octconv16 weight}" + (quoteDATA pl))));
}; # _parsers
# Update the documentation of services.tinydns.*.data when adding new record-building functions, here!
in _parsers