about summary refs log tree commit diff
path: root/users/Profpatsch/netstring
diff options
context:
space:
mode:
Diffstat (limited to 'users/Profpatsch/netstring')
-rw-r--r--users/Profpatsch/netstring/README.md18
-rw-r--r--users/Profpatsch/netstring/default.nix69
-rw-r--r--users/Profpatsch/netstring/tests/default.nix64
3 files changed, 151 insertions, 0 deletions
diff --git a/users/Profpatsch/netstring/README.md b/users/Profpatsch/netstring/README.md
new file mode 100644
index 0000000000..b8daea11d1
--- /dev/null
+++ b/users/Profpatsch/netstring/README.md
@@ -0,0 +1,18 @@
+# Netstring
+
+Netstrings are a djb invention. They are intended as a serialization format. Instead of inline control characters like `\n` or `\0` to signal the end of a string, they use a run-length encoding given as the number of bytes, encoded in ASCII, at the beginning of the string.
+
+```
+hello -> 5:hello,
+foo! -> 4:foo!,
+こんにちは -> 15:こんにちは,
+```
+
+They can be used to encode e.g. lists by simply concatenating and reading them in one-by-one.
+
+If you need a more complex encoding, you could start encoding e.g. tuples as netstrings-in-netstrings, or you could use [`netencode`](../netencode/README.md) instead, which is what-if-json-but-netstrings, and takes the idea of netstrings to their logical conclusion.
+
+Resources:
+
+Spec: http://cr.yp.to/proto/netstrings.txt
+Wiki: https://en.wikipedia.org/wiki/Netstring
diff --git a/users/Profpatsch/netstring/default.nix b/users/Profpatsch/netstring/default.nix
new file mode 100644
index 0000000000..e85cf24dd8
--- /dev/null
+++ b/users/Profpatsch/netstring/default.nix
@@ -0,0 +1,69 @@
+{ lib, pkgs, depot, ... }:
+let
+  toNetstring = s:
+    "${toString (builtins.stringLength s)}:${s},";
+
+  toNetstringList = xs:
+    lib.concatStrings (map toNetstring xs);
+
+  toNetstringKeyVal = attrs:
+    lib.concatStrings
+      (lib.mapAttrsToList
+        (k: v: toNetstring (toNetstring k + toNetstring v))
+        attrs);
+
+  python-netstring = depot.users.Profpatsch.writers.python3Lib
+    {
+      name = "netstring";
+    } ''
+    def read_netstring(bytes):
+        (int_length, rest) = bytes.split(sep=b':', maxsplit=1)
+        val = rest[:int(int_length)]
+        # has to end on a ,
+        assert(rest[len(val)] == ord(','))
+        return (val, rest[len(val) + 1:])
+
+    def read_netstring_key_val(bytes):
+        (keyvalnet, rest) = read_netstring(bytes)
+        (key, valnet) = read_netstring(keyvalnet)
+        (val, nothing) = read_netstring(valnet)
+        assert(nothing == b"")
+        return (key, val, rest)
+
+    def read_netstring_key_val_list(bytes):
+        rest = bytes
+        res = {}
+        while rest != b"":
+            (key, val, r) = read_netstring_key_val(rest)
+            rest = r
+            res[key] = val
+        return res
+  '';
+
+  rust-netstring = depot.nix.writers.rustSimpleLib
+    {
+      name = "netstring";
+    } ''
+    pub fn to_netstring(s: &[u8]) -> Vec<u8> {
+        let len = s.len();
+        // length of the integer as ascii
+        let i_len = ((len as f64).log10() as usize) + 1;
+        let ns_len = i_len + 1 + len + 1;
+        let mut res = Vec::with_capacity(ns_len);
+        res.extend_from_slice(format!("{}:", len).as_bytes());
+        res.extend_from_slice(s);
+        res.push(b',');
+        res
+    }
+  '';
+
+in
+depot.nix.readTree.drvTargets {
+  inherit
+    toNetstring
+    toNetstringList
+    toNetstringKeyVal
+    python-netstring
+    rust-netstring
+    ;
+}
diff --git a/users/Profpatsch/netstring/tests/default.nix b/users/Profpatsch/netstring/tests/default.nix
new file mode 100644
index 0000000000..6a1062988f
--- /dev/null
+++ b/users/Profpatsch/netstring/tests/default.nix
@@ -0,0 +1,64 @@
+{ depot, lib, pkgs, ... }:
+
+let
+
+  python-netstring-test = depot.users.Profpatsch.writers.python3
+    {
+      name = "python-netstring-test";
+      libraries = p: [
+        depot.users.Profpatsch.netstring.python-netstring
+      ];
+    } ''
+    import netstring
+
+    def assEq(left, right):
+      assert left == right, "{} /= {}".format(str(left), str(right))
+
+    assEq(
+      netstring.read_netstring(b"""${depot.nix.netstring.fromString "hi!"}"""),
+      (b"hi!", b"")
+    )
+
+    assEq(
+      netstring.read_netstring_key_val(
+        b"""${depot.nix.netstring.attrsToKeyValList { foo = "42"; }}"""
+      ),
+      (b'foo', b'42', b"")
+    )
+
+    assEq(
+      netstring.read_netstring_key_val_list(
+        b"""${depot.nix.netstring.attrsToKeyValList { foo = "42"; bar = "hi"; }}"""
+      ),
+      { b'foo': b'42', b'bar': b'hi' }
+    )
+  '';
+
+  rust-netstring-test = depot.nix.writers.rustSimple
+    {
+      name = "rust-netstring-test";
+      dependencies = [
+        depot.users.Profpatsch.netstring.rust-netstring
+      ];
+    } ''
+    extern crate netstring;
+
+    fn main() {
+      assert_eq!(
+        std::str::from_utf8(&netstring::to_netstring(b"hello")).unwrap(),
+        r##"${depot.nix.netstring.fromString "hello"}"##
+      );
+      assert_eq!(
+        std::str::from_utf8(&netstring::to_netstring("こんにちは".as_bytes())).unwrap(),
+        r##"${depot.nix.netstring.fromString "こんにちは"}"##
+      );
+    }
+  '';
+
+in
+depot.nix.readTree.drvTargets {
+  inherit
+    python-netstring-test
+    rust-netstring-test
+    ;
+}