{ 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