From 3ffa4b10afcec4eb8c98ba5078d674787e480f14 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Tue, 25 Oct 2016 16:16:08 +1030 Subject: [PATCH] Add Swift keyring client API with tests Add a swift-client-api subdirectory containing a Swift source package and a Makefile.in that compiles it into the "ServalClient" Swift module using the Swift package manager. The Swift API contains the following classes: - ServalKeyring provides the operations: add, remove, set, list - AbstractId and its specialisation SubscriberId, already in near-final form, are data types for SID and the like - ServalRestfulClient (internal) uses an HTTP client to access the Serval DNA RESTful interface Improve the REST /keyring/set operation to only alter the DID or Name if the corresponding query parameter is supplied. Modify the internal keyring_set_did() function to only assign the DID or Name if the corresponding parameter is not a null pointer. The configure script ensures that the Swift build target version is 10.10 or later when compiling for Mac OS-X, so that the package manager will succeed. Add autoconf macros for the Swift package manager. --- .gitignore | 4 + INSTALL.md | 15 ++ Makefile.in | 30 ++- configure.ac | 105 ++++++-- doc/Development.md | 38 ++- doc/REST-API-Keyring.md | 4 + keyring.c | 14 +- keyring_restful.c | 2 +- m4/ax_prog_swift_build_works.m4 | 96 +++++++ m4/ax_prog_swift_package_manager.m4 | 70 +++++ m4/ax_prog_swift_package_works.m4 | 86 +++++++ swift-client-api/Makefile.in | 56 ++++ swift-client-api/client_util.swift | 218 ++++++++++++++++ swift-client-api/package/Package.swift | 7 + .../package/Sources/abstract_id.swift | 109 ++++++++ swift-client-api/package/Sources/debug.swift | 31 +++ .../package/Sources/keyring.swift | 240 ++++++++++++++++++ .../package/Sources/restful_client.swift | 119 +++++++++ .../package/Sources/subscriber_id.swift | 32 +++ testconfig.sh.in | 1 + testdefs.sh | 12 +- testdefs_swift.sh | 34 +++ tests/all | 4 + tests/keyringswift | 155 +++++++++++ 24 files changed, 1453 insertions(+), 29 deletions(-) create mode 100644 m4/ax_prog_swift_build_works.m4 create mode 100644 m4/ax_prog_swift_package_manager.m4 create mode 100644 m4/ax_prog_swift_package_works.m4 create mode 100644 swift-client-api/Makefile.in create mode 100644 swift-client-api/client_util.swift create mode 100644 swift-client-api/package/Package.swift create mode 100644 swift-client-api/package/Sources/abstract_id.swift create mode 100644 swift-client-api/package/Sources/debug.swift create mode 100644 swift-client-api/package/Sources/keyring.swift create mode 100644 swift-client-api/package/Sources/restful_client.swift create mode 100644 swift-client-api/package/Sources/subscriber_id.swift create mode 100644 testdefs_swift.sh create mode 100755 tests/keyringswift diff --git a/.gitignore b/.gitignore index f849c226..6e6eacd5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /objs /objs_lib /objs_servald +*.o *.a *.suo .*.sw? @@ -36,3 +37,6 @@ test.*.log /java-api/Makefile /java-api/classes/ /java-api/testclasses/ +/swift-client-api/Makefile +/swift-client-api/build/ +/swift-client-api/swift-client-util diff --git a/INSTALL.md b/INSTALL.md index c821cbcd..899f94aa 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -150,6 +150,14 @@ A successful session should appear something like: Note: Some input files use or override a deprecated API. Note: Recompile with -Xlint:deprecation for details. make[1]: Leaving directory '/home/username/src/serval-dna/java-api' + SWIFT MODULE servald.swiftmodule + cd swift-api && make SOURCE_PREFIX= all + make[1]: Entering directory '/home/username/src/serval-dna/swift-api' + swift build --package-path package + Compile Swift Module 'ServalClient' (5 sources) + Archiving /home/username/src/serval-dna/swift-api/package/.build/x86_64-unknown-linux/debug/libServalClient.a + SWIFT test-swift + make[1]: Leaving directory '/home/username/src/serval-dna/swift-api' rm _servalclient.a _monitorclient.a _servald.a $ @@ -251,6 +259,11 @@ present][Swift development]: Swift language's run-time support and resultant library dependencies, so is not suitable for deployment. +* **swift-client-api/build/debug/ServalClient.swiftmodule** + **swift-client-api/build/debug/ServalClient.swiftdoc** + **swift-client-api/build/debug/libServalClient.a** + are the [Swift Client API module][]. + Test scripts ------------ @@ -317,6 +330,7 @@ This document is available under the [Creative Commons Attribution 4.0 Internati [gcc 6]: http://gcc.gnu.org/gcc-6/ [Notes for Developers]: ./doc/Development.md [Swift development]: ./doc/Development.md#swift +[Swift Client API module]: ./doc/Development.md#swift-client-api [OpenWRT]: ./doc/OpenWRT.md [Serval Infrastructure]: ./doc/Serval-Infrastructure.md [Serval Mesh Extender]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:meshextender: @@ -329,6 +343,7 @@ This document is available under the [Creative Commons Attribution 4.0 Internati [CLI API]: ./doc/CLI-API.md [JNI]: http://en.wikipedia.org/wiki/Java_Native_Interface [Swift]: https://en.wikipedia.org/wiki/Swift_(programming_language) +[dlopen(3)]: http://man7.org/linux/man-pages/man3/dlopen.3.html [Bash]: http://en.wikipedia.org/wiki/Bash_(Unix_shell) [GNU make]: http://www.gnu.org/software/make/ [Git]: http://git-scm.com/ diff --git a/Makefile.in b/Makefile.in index 9fc37ec2..37a0d88e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in for Serval DNA +# Makefile.in for Serval DNA daemon and libraries # vim: noet ts=8 sts=0 sw=8 prefix=@prefix@ exec_prefix=@exec_prefix@ @@ -88,6 +88,7 @@ CFLAGS+=-DSQLITE_THREADSAFE=0 \ -DSQLITE_OMIT_VIRTUALTABLE \ -DSQLITE_OMIT_AUTHORIZATION CFLAGS+=-fPIC -DSERVAL_ENABLE_DEBUG=1 -Wall -Werror -Wextra -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 +CFLAGS+=@CFLAGS_TARGET@ # Solaris magic CFLAGS+=-DSHA2_USE_INTTYPES_H -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED=1 -D__EXTENSIONS__=1 @@ -163,6 +164,25 @@ java-api-clean: endif # $(JAVAC) +ifneq ($(SWIFTC),) # Only provide Swift targets if the Swift compiler is available. + +all: swift-client-api + +test: servaldswift + +clean: swift-client-api-clean + +.PHONY: swift-client-api swift-client-api-clean + +swift-client-api: + @mkdir -p swift-client-api + cd swift-client-api && $(MAKE) all + +swift-client-api-clean: + cd swift-client-api 2>/dev/null && $(MAKE) clean + +endif # $(SWIFTC) + # Build the Sodium elliptic curve encryption library within its 'libsodium' # subtree, and install its development files (headers and libraries) into our # $(LIBSODIUM_DEV) subdirectory. Then use the contents of this subdirectory to @@ -186,7 +206,11 @@ $(LIBSODIUM_DEV)/.installed: @mkdir -p $(LIBSODIUM_DEV) @$(RM) $@ @touch $@.in-progress - @cd $(LIBSODIUM_SUBDIR) && $(MAKE) CFLAGS+=-prefer-pic prefix=$(abspath $(LIBSODIUM_DEV)) install + @cd $(LIBSODIUM_SUBDIR) && $(MAKE) \ + CFLAGS+="-prefer-pic @CFLAGS_TARGET@" \ + CCASFLAGS+="@CFLAGS_TARGET@" \ + prefix=$(abspath $(LIBSODIUM_DEV)) \ + install @mv -f $@.in-progress $@ # Source code test coverage support -- see doc/Testing.md @@ -338,8 +362,6 @@ servald: $(OBJSDIR_SERVALD)/servald_features.o \ ifneq ($(SWIFTC),) # Only provide Swift targets if the Swift compiler is available. -test: servaldswift - # A servald equivalent whose main entry point and logging output is implemented # in the Swift language, rather than the C entry point in main.c. This exists # mainly to ensure that the Serval daemon library can be linked into a Swift diff --git a/configure.ac b/configure.ac index 3ef2fdf6..061fd2b6 100644 --- a/configure.ac +++ b/configure.ac @@ -6,11 +6,11 @@ AC_CONFIG_MACRO_DIR([m4]) AC_ARG_VAR([AR], [Library archiver]) AC_ARG_VAR([RANLIB], [Archive indexer]) + +dnl Specify Swift compiler AC_ARG_VAR([SWIFTC], [Swift compiler]) AC_ARG_VAR([SWIFTCFLAGS], [Swift compiler flags]) -CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" - dnl Specify default instance path AC_ARG_VAR([INSTANCE_PATH], [default instance path for servald]) AS_IF([test "x$INSTANCE_PATH" != x], [AC_DEFINE_UNQUOTED([INSTANCE_PATH], ["$INSTANCE_PATH"], [default instance path])]) @@ -39,23 +39,61 @@ dnl Specify default Rhizome store directory AC_ARG_VAR([RHIZOME_STORE_PATH], [default Rhizome store directory]) AS_IF([test "x$RHIZOME_STORE_PATH" != x], [AC_DEFINE_UNQUOTED([RHIZOME_STORE_PATH], ["$RHIZOME_STORE_PATH"], [default Rhizome store directory])]) -dnl Set $host_os, which is needed by javac detection. -AC_CANONICAL_SYSTEM +dnl Set $build, $build_cpu, $build_vendor, $build_os to reflect the native +dnl platform (the one on which the build is being performed), either from the +dnl --build option, or by running config.guess. +AC_CANONICAL_BUILD -dnl Check for C99 compiler and other tools. +dnl Set $host, $host_cpu, $host_vendor, $host_os, either from --host option +dnl (cross compilation), or falling back to $build (native compilation). +AC_CANONICAL_HOST + +dnl $CFLAGS_TARGET is used in the Serval DNA Makefile and also passed in CFLAGS to all +dnl sub-Makefiles, such as libsodium. +CFLAGS_TARGET='' + +dnl Check for C99 compiler, preprocessor and assembler. AC_PROG_CC_C99 AC_PROG_CPP +AM_PROG_AS +dnl Check for library creation tools. AC_CHECK_TOOL([AR], [ar], [:]) AS_IF([test "x$AR" = x:], [AC_MSG_ERROR([Library archiver not found: ar])]) - AC_CHECK_TOOL([RANLIB], [ranlib], [:]) AS_IF([test "x$RANLIB" = x:], [AC_MSG_ERROR([Archive indexer not found: ranlib])]) +dnl C preprocessor option to support cross-compiling. +AX_APPEND_COMPILE_FLAGS(["-arch $host_cpu"], [CPPFLAGS]) + dnl Check for a Swift 3 or 4 compiler; set SWIFTC if found. AC_PROG_SWIFTC AS_IF([test "x$SWIFTC" != x], [ dnl + dnl Discover the Swift compiler's target, which is determined by the --host option + dnl in an Xcode cross build, and is its default target for a native build (which + dnl may be affected by $SWIFTCFLAGS). + AC_MSG_CHECKING([Swift target]) + + dnl Swift SDK names differ a little from Xcode names: iPhoneOS and iPhoneSimulator + dnl are both represented as ios. + swiftc_target=`$SWIFTC $SWIFTCFLAGS -version 2>&1 | sed -n -e 's/^Target: *//p'` + AS_IF([test "x$swiftc_target" = x], [AC_MSG_ERROR([Swift compiler does not report its target: $SWIFTC $SWIFTCFLAGS -version])]) + + dnl In a build for Mac OSX (native or cross), ensure that the Swift target is high + dnl enough to create a Swift package, which were not supported before Mac OSX 10.10. + macosx_min_version=10.10 + AS_CASE([$swiftc_target], + [[*-apple-macosx*]], [ dnl + swiftc_target_version=[`echo "$swiftc_target" | sed -n -e 's/^.*-macosx//p'`] + AS_VERSION_COMPARE([$swiftc_target_version], [$macosx_min_version], [ dnl + CFLAGS_TARGET="-mmacosx-version-min=$macosx_min_version" + swiftc_target=[`echo "$swiftc_target" | sed -n -e 's/-macosx.*$//p'`-macosx$macosx_min_version] + ]) + ] + ) + AC_MSG_RESULT([$swiftc_target]) + dnl Check whether the Swift compiler will compile a simple Swift 4 program with the supplied flags. dnl If not, report failure but keep going. AC_PROG_SWIFTC_IS_SWIFT4 @@ -67,11 +105,31 @@ AS_IF([test "x$SWIFTC" != x], [ dnl SWIFTC= ]) ]) + + dnl Add the -target option to SWIFTCFLAGS (if the user has already supplied a -target option in + dnl SWIFTCFLAGS, then this prepended one will take precedence). + SWIFTCFLAGS="-target $swiftc_target $SWIFTCFLAGS" ]) +dnl Check for a working Swift package manager, keep going if unsuccessful. +AS_IF([test "x$SWIFTC" != x && test "x$SWIFT_BUILD" = x], [ + AC_PROG_SWIFT_PACKAGE_MANAGER +]) + +dnl The C compilation flags used in all Serval-DNA Makefiles. +CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" +CFLAGS_TARGET="$CFLAGS_TARGET" +CFLAGS="$CFLAGS_TARGET $CFLAGS" + +export CPPFLAGS +export CFLAGS + +AC_SUBST([WRAPPER]) +AC_SUBST([CFLAGS_TARGET]) AC_SUBST([SWIFTC]) AC_SUBST([SWIFTCFLAGS]) AC_SUBST([SWIFT_VERSION]) +AC_SUBST([SWIFT_BUILD]) dnl Various GCC function and variable attributes AX_GCC_FUNC_ATTRIBUTE(aligned) @@ -90,8 +148,16 @@ dnl Swift compiler, so the gold linker is used to create dynamic libraries. dnl See doc/Development.md for details. AX_APPEND_LINK_FLAGS(-fuse-ld=gold) -dnl Check compiler options for compiling SQLite. -AX_APPEND_COMPILE_FLAGS( +dnl Early versions of GCC on Mac OS-X would fail without this flag, which soon +dnl became deprecated and eventually removed some time around 2015. +AX_APPEND_COMPILE_FLAGS([-no-cpp-precomp], [CFLAGS]) + +dnl Cause GCC to use pipes to connect the compilation stages using pipes instead +dnl of temporary files. +AX_APPEND_COMPILE_FLAGS([-pipe], [CFLAGS]) + +dnl Suppress certain compiler warnings when compiling SQLite. +AX_APPEND_COMPILE_FLAGS([ \ -Wno-empty-body \ -Wno-unused-value \ -Wno-unused-function \ @@ -99,8 +165,12 @@ AX_APPEND_COMPILE_FLAGS( -Wno-unused-variable \ -Wno-unused-but-set-variable \ -Wno-missing-field-initializers \ - -Wno-deprecated-declarations, -[CFLAGS_SQLITE], [-Werror]) + -Wno-deprecated-declarations \ + '-Wno-#warnings' \ + ], + [CFLAGS_SQLITE], [-Werror]) +dnl Put a backslash before any '#' characters so it expands correctly in Makefile.in. +CFLAGS_SQLITE="`echo "$CFLAGS_SQLITE" | sed 's/#/\\\\#/g'`" AC_SUBST([CFLAGS_SQLITE]) dnl Math library functions for spandsp @@ -158,7 +228,7 @@ AC_CHECK_TYPES([off64_t], [have_off64_t=1], [have_off64_t=0]) AC_CHECK_SIZEOF([off_t]) dnl There must be a 64-bit seek(2) system call of some kind -AS_IF([test "x$have_lseek64_t" = "xno" -a "x$ac_cv_sizeof_off_t" != x8 ], [ +AS_IF([test "x$have_lseek64_t" = "xno" && test "x$ac_cv_sizeof_off_t" != x8 ], [ AC_MSG_ERROR([Missing lseek64(2) system call]) ]) @@ -205,11 +275,6 @@ AC_CHECK_HEADERS( AC_SUBST([HAVE_JNI_H], [$ac_cv_header_jni_h]) -dnl The entire libsodium source is in a subdirectory, and has its own configure -dnl script. - -AC_CONFIG_SUBDIRS([libsodium]) - dnl Check if the Linux gettid() and tgkill() system calls are supported. AC_CHECK_FUNCS([gettid tgkill]) AC_CACHE_CHECK([Linux thread system calls], ac_cv_have_linux_threads, [ @@ -284,8 +349,16 @@ dnl them in @DEFS@ on the command-line of every compilation invoked by make. AC_CONFIG_HEADERS([config.h]) AC_SUBST([CONFIG_H], [config.h]) +dnl The entire libsodium source is in a subdirectory, and has its own configure +dnl script. + +AC_CONFIG_SUBDIRS([libsodium]) + +ac_configure_args="$ac_configure_args CC='$CC' CCAS='$CCAS' LD='$LD'" + AC_OUTPUT([ Makefile testconfig.sh java-api/Makefile + swift-client-api/Makefile ]) diff --git a/doc/Development.md b/doc/Development.md index 1110ae67..ce24c5dc 100644 --- a/doc/Development.md +++ b/doc/Development.md @@ -159,9 +159,9 @@ Apple Mac OS X ### Test utilities The [OS X grep(1)][] , [OS X sed(1)][] and [OS X awk(1)][] tools provided by -Apple Mac OS X are the BSD variants. The test scripts require the GNU variants -with the names *ggrep*, *gsed* and *gawk*, which can be installed on Mac OS X -using the [homebrew][] package manager: +Apple Mac OS X are the BSD variants. The [test scripts][] require the GNU +variants with the names *ggrep*, *gsed* and *gawk*, which can be installed on +Mac OS X using the [homebrew][] package manager: $ brew tap homebrew/dupes ==> Tapping homebrew/dupes @@ -255,6 +255,34 @@ environment variables or using the `VARNAME=value` syntax on its command line: * `SWIFTCFLAGS` extra command-line arguments to pass to the Swift compiler; analogous to `CFLAGS` for the C compiler +Swift Client API +---------------- + +Serval DNA provides a *Swift Client API* as a [Swift module][] called +**ServalClient**, which provides access to the services of the Serval DNA +daemon through its [REST API][]. + +The Swift client API is written entirely in [Swift][] using the [URLSession][] +Foundation class and related classes as the HTTP client. The API is covered +by its own [test scripts][]. To date only the following parts of the [REST +API][] are supported: + +* [Keyring REST API][], test script is [keyringswift](../tests/keyringswift) + +Using Swift modules +------------------- + +To use a [Swift module][] in your Swift program: + +* add the directory containing the *ModuleName.swiftmodule* and + *ModuleName.swiftdoc* files to your Swift import path, so the Swift source + code can use `import ModuleName` to access the API definition; + +* include the `libModuleName.a` static library in the link command line, either + by giving its path explicitly as an argument, or by adding its containing + directory to the link search path with the `-L` option and giving the + `-lModuleName` option on the link command line. + About the examples ------------------ @@ -279,6 +307,9 @@ Available under the [Creative Commons Attribution 4.0 International licence][CC [CC BY 4.0]: ../LICENSE-DOCUMENTATION.md [Serval DNA]: ../README.md [build]: ../INSTALL.md +[REST API]: ./REST-API.md +[Keyring REST API]: ./REST-API-Keyring.md +[test scripts]: ./Testing.md [configure.ac]: ../configure.ac [autoconf]: http://www.gnu.org/software/autoconf/autoconf.html [autoconf macro archive]: http://www.gnu.org/software/autoconf-archive/ @@ -307,5 +338,6 @@ Available under the [Creative Commons Attribution 4.0 International licence][CC [Swift module]: https://swift.org/package-manager/#modules [Swift 3]: https://swift.org/blog/swift-3-0-released/ [Swift 4]: https://swift.org/blog/swift-4-0-released/ +[URLSession]: https://developer.apple.com/documentation/foundation/urlsession [gold]: https://en.wikipedia.org/wiki/Gold_(linker) [Bourne shell]: http://en.wikipedia.org/wiki/Bourne_shell diff --git a/doc/REST-API-Keyring.md b/doc/REST-API-Keyring.md index 02718c50..64749b5f 100644 --- a/doc/REST-API-Keyring.md +++ b/doc/REST-API-Keyring.md @@ -210,6 +210,10 @@ recognised: * **name**: the name; empty to clear the name, otherwise must conform to the rules for [Name](#name) +If a parameter is missing, then the corresponding field of the identity is left +unchanged. If a parameter is set to an empty string, then the corresponding +field of the identity is erased. + If any parameter contains an invalid value then the request returns [400 Bad Request][400]. If there is no unlocked identity with the given SID, this request returns [404 Not Found][404]. diff --git a/keyring.c b/keyring.c index 554bc368..dbd909fa 100644 --- a/keyring.c +++ b/keyring.c @@ -1612,6 +1612,10 @@ int keyring_commit(keyring_file *k) int keyring_set_did_name(keyring_identity *id, const char *did, const char *name) { + /* Do nothing if not changing either field. */ + if (!did && !name) + return 0; + /* Find where to put it */ keypair *kp = id->keypairs; while(kp){ @@ -1628,26 +1632,28 @@ int keyring_set_did_name(keyring_identity *id, const char *did, const char *name return -1; keyring_identity_add_keypair(id, kp); DEBUG(keyring, "Created DID/Name record for identity"); + bzero(kp->private_key, kp->private_key_len); + bzero(kp->public_key, kp->public_key_len); } /* Store DID as nul-terminated string. */ - { + if (did) { size_t len = strlen(did); assert(len < kp->private_key_len); bcopy(did, &kp->private_key[0], len); bzero(&kp->private_key[len], kp->private_key_len - len); + DEBUG_dump(keyring, "storing DID", &kp->private_key[0], kp->private_key_len); } /* Store Name as nul-terminated string. */ - { + if (name) { size_t len = strlen(name); assert(len < kp->public_key_len); bcopy(name, &kp->public_key[0], len); bzero(&kp->public_key[len], kp->public_key_len - len); + DEBUG_dump(keyring, "storing Name", &kp->public_key[0], kp->public_key_len); } - DEBUG_dump(keyring, "storing DID",&kp->private_key[0],32); - DEBUG_dump(keyring, "storing Name",&kp->public_key[0],64); return 0; } diff --git a/keyring_restful.c b/keyring_restful.c index 31c1ec93..06463b76 100644 --- a/keyring_restful.c +++ b/keyring_restful.c @@ -272,7 +272,7 @@ static int restful_keyring_set(httpd_request *r, const char *remainder) keyring_identity *id = keyring_find_identity_sid(keyring, &r->sid1); if (!id) return http_request_keyring_response(r, 404, "Identity not found"); - if (keyring_set_did_name(id, did ? did : "", name ? name : "") == -1) + if (keyring_set_did_name(id, did, name) == -1) return http_request_keyring_response(r, 500, "Could not set identity DID/Name"); if (keyring_commit(keyring) == -1) return http_request_keyring_response(r, 500, "Could not store new identity"); diff --git a/m4/ax_prog_swift_build_works.m4 b/m4/ax_prog_swift_build_works.m4 new file mode 100644 index 00000000..49ba3039 --- /dev/null +++ b/m4/ax_prog_swift_build_works.m4 @@ -0,0 +1,96 @@ +# Serval Project Swift language support +# +# SYNOPSIS +# +# AX_PROG_SWIFT_BUILD_WORKS +# +# DESCRIPTION +# +# AX_PROG_SWIFT_BUILD_WORKS tests whether the Swift package manager "build" +# command works. +# +# Requires the SWIFT shell variable to contain the path of the Swift package +# manager executable (not the Swift compiler!), either relative to $PATH or +# absolute; this will usually be just "swift". Expands the SWIFTCFLAGS shell +# variable on the build command-line with -Xswiftc preceding each word. +# +# Sets the SWIFT_BUILD variable to the command to invoke the build-package +# command, usually "$SWIFT build". +# +# To force a specific swift executable, either: +# +# - in configure.ac, set SWIFT=yourswift before calling +# AX_PROG_SWIFT_PACKAGE_MANAGER, or +# +# - before invoking ./configure, export SWIFT=yourswift +# +# LICENSE +# +# Copyright (C) 2016 Flinders University +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +AU_ALIAS([AC_PROG_SWIFT_BUILD_WORKS], [AX_PROG_SWIFT_BUILD_WORKS]) +AC_DEFUN([AX_PROG_SWIFT_BUILD_WORKS],[ + AC_REQUIRE([AX_TMPDIR_SWIFT]) + SWIFT_BUILD="$SWIFT build" + AC_CACHE_CHECK([if $SWIFT_BUILD works], ac_cv_prog_swift_build_works, [ + AS_MKDIR_P(["$ax_tmpdir_swift/swift_package/Sources"]) + cat << EOF > "$ax_tmpdir_swift/swift_package/Package.swift" +/* [#]line __oline__ "configure" */ +import PackageDescription +let package = Package(name: "test") +EOF + cat << EOF > "$ax_tmpdir_swift/swift_package/Sources/test.swift" +/* [#]line __oline__ "configure" */ +public func test() { + test1() +} +fileprivate func test1() {} +EOF + ac_cv_prog_swift_build_works=no + SWIFTBUILDFLAGS= + for flag in $SWIFTCFLAGS; do + AS_VAR_APPEND([SWIFTBUILDFLAGS], [" -Xswiftc $flag"]) + done + AS_IF([AC_TRY_COMMAND(cd "$ax_tmpdir_swift/swift_package" && $SWIFT_BUILD $SWIFTBUILDFLAGS) >/dev/null 2>&1], [ + AS_IF([test -e "$ax_tmpdir_swift/swift_package/.build/debug/test.swiftmodule"], [ + ac_cv_prog_swift_build_works=yes + ]) + ]) + AS_IF([test "x$ac_cv_prog_swift_build_works" != xyes], [ + echo "Package.swift:" >&AS_MESSAGE_LOG_FD + cat "$ax_tmpdir_swift/swift_package/Package.swift" >&AS_MESSAGE_LOG_FD + echo "failed program was:" >&AS_MESSAGE_LOG_FD + cat "$ax_tmpdir_swift/swift_package/Sources/test.swift" >&AS_MESSAGE_LOG_FD + ]) + ]) + AS_IF([test "x$ac_cv_prog_swift_build_works" != xyes], [ + AS_UNSET([SWIFT_BUILD]) + ]) + AC_PROVIDE([$0])dnl +]) diff --git a/m4/ax_prog_swift_package_manager.m4 b/m4/ax_prog_swift_package_manager.m4 new file mode 100644 index 00000000..0edaaa2c --- /dev/null +++ b/m4/ax_prog_swift_package_manager.m4 @@ -0,0 +1,70 @@ +# Serval Project Swift language support +# +# SYNOPSIS +# +# AX_PROG_SWIFT_PACKAGE_MANAGER +# +# DESCRIPTION +# +# AX_PROG_SWIFT_PACKAGE_MANAGER tests for the presence of a Swift package +# manager, ie, a "swift" executable that supports the "swift build" and +# "swift package" commands. +# +# Sets the SWIFT shell variable to the name of the Swift executable (not the +# Swift compiler!) either relative to $PATH or an absolute path if necessary; +# this is usually just "swift". +# +# Sets the SWIFT_BUILD shell variable to the command to invoke the +# build-package command, usually "$SWIFT build". +# +# Sets the SWIFT_PACKAGE variable to the command to invoke the package +# management command, usually "$SWIFT package". +# +# To force a specific swift executable, either: +# +# - in configure.ac, set SWIFT=yourswift before calling +# AX_PROG_SWIFT_PACKAGE_MANAGER, or +# +# - before invoking ./configure, export SWIFT=yourswift +# +# LICENSE +# +# Copyright (C) 2016 Flinders University +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +AU_ALIAS([AC_PROG_SWIFT_PACKAGE_MANAGER], [AX_PROG_SWIFT_PACKAGE_MANAGER]) +AC_DEFUN([AX_PROG_SWIFT_PACKAGE_MANAGER],[ + AS_IF([test "x$SWIFT" = x], [AC_CHECK_PROGS([SWIFT], [swift])]) + AS_IF([test "x$SWIFT" = x], [ + echo "no Swift executable found in \$PATH" >&AS_MESSAGE_LOG_FD + ], [ + AX_PROG_SWIFT_BUILD_WORKS + AX_PROG_SWIFT_PACKAGE_WORKS + ]) + AC_PROVIDE([$0])dnl +]) diff --git a/m4/ax_prog_swift_package_works.m4 b/m4/ax_prog_swift_package_works.m4 new file mode 100644 index 00000000..e395a4d7 --- /dev/null +++ b/m4/ax_prog_swift_package_works.m4 @@ -0,0 +1,86 @@ +# Serval Project Swift language support +# +# SYNOPSIS +# +# AX_PROG_SWIFT_PACKAGE_WORKS +# +# DESCRIPTION +# +# AX_PROG_SWIFT_BUILD_WORKS tests whether the Swift package manager "package" +# command works. +# +# Requires the SWIFT shell variable to contain the path of the Swift package +# manager executable (not the Swift compiler!), either relative to $PATH or +# absolute; this will usually be just "swift". +# +# Sets the SWIFT_PACKAGE variable to the command to invoke the manage-package +# command, usually "swift package". +# +# To force a specific swift executable, either: +# +# - in configure.ac, set SWIFT=yourswift before calling +# AX_PROG_SWIFT_PACKAGE_MANAGER, or +# +# - before invoking ./configure, export SWIFT=yourswift +# +# LICENSE +# +# Copyright (C) 2016 Flinders University +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +AU_ALIAS([AC_PROG_SWIFT_PACKAGE_WORKS], [AX_PROG_SWIFT_PACKAGE_WORKS]) +AC_DEFUN([AX_PROG_SWIFT_PACKAGE_WORKS],[ + AC_REQUIRE([AC_PROG_SED]) + AC_REQUIRE([AX_TMPDIR_SWIFT]) + SWIFT_PACKAGE="$SWIFT package" + AC_CACHE_CHECK([if $SWIFT_PACKAGE works], ac_cv_prog_swift_package_works, [ + AS_MKDIR_P(["$ax_tmpdir_swift/swift_package/Sources"]) + cat << \EOF > "$ax_tmpdir_swift/swift_package/Package.swift" +/* [#]line __oline__ "configure" */ +import PackageDescription +let package = Package(name: "test") +EOF + ac_cv_prog_swift_package_works=no + AS_IF([AC_TRY_COMMAND(cd "$ax_tmpdir_swift/swift_package" >/dev/null && $SWIFT_PACKAGE dump-package) > "$ax_tmpdir_swift/swift_package_info.json"], [ + ac_swift_package_info=`$SED -e ':a;$!N;s/\n//;ta;s/ //g' "$ax_tmpdir_swift/swift_package_info.json"` + AS_IF([test "x$ac_swift_package_info" = ['x{"dependencies":[],"exclude":[],"name":"test","targets":[]}']], [ + ac_cv_prog_swift_package_works=yes + ]) + ]) + AS_IF([test "x$ac_cv_prog_swift_package_works" != xyes], [ + echo "Package.swift:" >&AS_MESSAGE_LOG_FD + cat "$ax_tmpdir_swift/swift_package/Package.swift" >&AS_MESSAGE_LOG_FD + echo "failed output was:" >&AS_MESSAGE_LOG_FD + echo "$ac_swift_package_info" >&AS_MESSAGE_LOG_FD + ]) + ]) + AS_IF([test "x$ac_cv_prog_swift_package_works" != xyes], [ + AS_UNSET([SWIFT_PACKAGE]) + ]) + AC_PROVIDE([$0])dnl +]) diff --git a/swift-client-api/Makefile.in b/swift-client-api/Makefile.in new file mode 100644 index 00000000..b5d4de64 --- /dev/null +++ b/swift-client-api/Makefile.in @@ -0,0 +1,56 @@ +# Makefile.in for Serval DNA Swift API +# vim: noet ts=8 sts=0 sw=8 +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +sbindir=@sbindir@ +sysconfdir=@sysconfdir@ +localstatedir=@localstatedir@ +srcdir=@srcdir@ +abs_builddir=@abs_builddir@ + +SWIFT_MODULE_NAME= ServalClient +SWIFT_PACKAGE_DIR= $(srcdir)/package +SWIFT_BUILD_DIR= $(abs_builddir)/build + +SWIFTC= @SWIFTC@ +SWIFTCFLAGS= @SWIFTCFLAGS@ +SWIFTCFLAGS+= -I $(srcdir) + +SWIFT_BUILD= @SWIFT_BUILD@ +SWIFT_BUILD_FLAGS= $(addprefix -Xswiftc , $(SWIFTCFLAGS)) + +DEFS= @DEFS@ +SWIFTDEFS= $(addprefix -Xcc , $(DEFS)) + +.PHONY: all check check_swiftc check_swift_build swiftmodule clean + +all: check swiftmodule swift-client-util + +check: check_swiftc check_swift_build + +check_swiftc: + @if [ -z "$(SWIFTC)" ]; then echo "No swift compiler configured" >&2; exit 1; fi + @if ! $(SWIFTC) -version >/dev/null; then echo "Swift compiler not executable" >&2; exit 1; fi + +check_swift_build: + @if [ -z "$(SWIFT_BUILD)" ]; then echo "No swift package manager configured" >&2; exit 1; fi + @if ! $(SWIFT_BUILD) -h >/dev/null; then echo "Swift package manager not executable" >&2; exit 1; fi + +# The Swift 3 package manager --chdir option was replaced with --package-path +# in Swift 4, so to support both, avoid the options altogether and use "cd +# package-dir && swift build" instead. +swiftmodule: + mkdir -p $(SWIFT_BUILD_DIR) && \ + cd $(SWIFT_PACKAGE_DIR) && \ + $(SWIFT_BUILD) --build-path $(SWIFT_BUILD_DIR) $(SWIFT_BUILD_FLAGS) $(SWIFTDEFS) + +$(SWIFT_BUILD_DIR)/debug/libServalClient.a: swiftmodule + +swift-client-util: $(srcdir)/client_util.swift $(SWIFT_BUILD_DIR)/debug/libServalClient.a + @echo SWIFT $@ + @$(SWIFTC) -emit-executable $(SWIFTCFLAGS) $(SWIFTDEFS) -I $(SWIFT_BUILD_DIR)/debug -o $@ $^ + +clean: + $(RM) -r $(SWIFT_BUILD_DIR) + $(RM) swift-client-util diff --git a/swift-client-api/client_util.swift b/swift-client-api/client_util.swift new file mode 100644 index 00000000..8e74c45b --- /dev/null +++ b/swift-client-api/client_util.swift @@ -0,0 +1,218 @@ +/* +Serval DNA Client Swift test program +Copyright (C) 2016-2017 Flinders University + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import ServalClient +import Dispatch +#if os(Linux) +import Glibc +#endif + +var arg0 : String = "" + +func usage() { + // Once no longer supporting Swift 3, change this to a multi-line string literal. + print("Usage: \(arg0) [options] keyring --pin PIN list") + print(" \(arg0) [options] keyring --pin PIN add [ did DID ] [ name NAME ]") + print(" \(arg0) [options] keyring --pin PIN remove SID") + print(" \(arg0) [options] keyring --pin PIN set SID [ did DID ] [ name NAME ]") + print("Options:") + print(" --pin PIN") + print(" --user USER") + print(" --password PASSWORD") +} + +func main() { + var args = CommandLine.arguments + arg0 = args.remove(at: 0) + var port : Int16? + var username : String? + var password : String? + parseOptions(&args, [ + ("--port", { port = Int16($0) }), + ("--user", { username = $0 }), + ("--password", { password = $0 }) + ]) + let restful_config = ServalRestfulClient.Configuration.default.with(port: port, username: username, password: password) + let cmd = args.isEmpty ? "" : args.remove(at: 0) + switch (cmd) { + case "keyring": + exit(keyring(&args, configuration: restful_config)) + default: + usage() + exit(1) + } +} + +func parseOptions(_ args: inout [String], _ options: [(String, (String) -> Void)]) -> Void { + argLoop: while (!args.isEmpty) { + let arg = args[0] + var opt : String + var param : String? + var optrange : Range + if let eq = arg.range(of: "=") { + opt = arg.substring(to: eq.lowerBound) + param = arg.substring(from: eq.upperBound) + optrange = 0 ..< 1 + } + else { + opt = arg + param = args.count > 1 ? args[1] : nil + optrange = 0 ..< 2 + } + for (label, closure) in options { + if opt == label && param != nil { + closure(param!) + args.removeSubrange(optrange) + continue argLoop + } + } + break argLoop + } +} + +func printIdentity(identity: ServalKeyring.Identity) { + print("sid:" + identity.sid.hexUpper) + print("identity:" + identity.identity.hexUpper) + if identity.did != nil { + print("did:" + identity.did!) + } + if identity.name != nil { + print("name:" + identity.name!) + } +} + +func keyring(_ args: inout [String], configuration: ServalRestfulClient.Configuration) -> Int32 { + let cmd = args.isEmpty ? "" : args.remove(at: 0) + var pin : String? = nil + parseOptions(&args, [("--pin", { pin = $0 })]) + var status : Int32 = 0 + switch (cmd) { + case "list": + precondition(args.isEmpty) + print("4") + print("sid:identity:did:name") + let semaphore = DispatchSemaphore(value: 0) + let client = ServalRestfulClient(configuration: configuration) + let request = ServalKeyring.listIdentities(client: client, pin: pin) { (identities, error) in + if let error = error { + print(error, to: &errorStream) + status = 2 + } + else if let identities = identities { + for identity in identities { + print("\(identity.sid.hexUpper):\(identity.identity.hexUpper):\(identity.did ?? ""):\(identity.name ?? "")") + } + } + semaphore.signal() + } + print("Waiting...", to: &debugStream) + semaphore.wait() + print("Done", to: &debugStream) + request.close() + + case "add": + var did : String? = nil + var name : String? = nil + parseOptions(&args, [("did", { did = $0 }), ("name", { name = $0 })]) + precondition(args.isEmpty) + var message = "Adding (did=" + debugPrint(did as Any, terminator:"", to:&message) + message += " name=" + debugPrint(name as Any, terminator:"", to:&message) + message += " pin=" + debugPrint(pin as Any, terminator:"", to:&message) + message += ")..." + print(message, to: &debugStream) + let semaphore = DispatchSemaphore(value: 0) + let client = ServalRestfulClient(configuration: configuration) + let request = ServalKeyring.addIdentity(client: client, did: did, name: name, pin: pin) { (identity, error) in + if let error = error { + print(error, to: &errorStream) + status = 2 + } + else if let identity = identity { + printIdentity(identity: identity) + } + semaphore.signal() + } + print("Waiting...", to: &debugStream) + semaphore.wait() + print("Done", to: &debugStream) + request.close() + + case "remove": + let sid = SubscriberId(fromHex: args.remove(at: 0))! + precondition(args.isEmpty) + print("Removing (sid=\(sid.hexUpper))...") + let semaphore = DispatchSemaphore(value: 0) + let client = ServalRestfulClient(configuration: configuration) + let request = ServalKeyring.removeIdentity(client: client, sid: sid, pin: pin) { (identity, error) in + if let error = error { + print(error, to: &errorStream) + status = 2 + } + else if let identity = identity { + printIdentity(identity: identity) + } + semaphore.signal() + } + print("Waiting...", to: &debugStream) + semaphore.wait() + print("Done", to: &debugStream) + request.close() + + case "set": + let sid = SubscriberId(fromHex: args.remove(at: 0))! + var did : String? = nil + var name : String? = nil + parseOptions(&args, [("did", { did = $0 }), ("name", { name = $0 })]) + precondition(args.isEmpty) + var message = "Setting (sid=\(sid.hexUpper))..." + message += " did=" + debugPrint(did as Any, terminator:"", to:&message) + message += " name=" + debugPrint(name as Any, terminator:"", to:&message) + message += " pin=" + debugPrint(pin as Any, terminator:"", to:&message) + print(message, to: &debugStream) + let semaphore = DispatchSemaphore(value: 0) + let client = ServalRestfulClient(configuration: configuration) + let request = ServalKeyring.setIdentity(client: client, sid: sid, did: did, name: name, pin: pin) { (identity, error) in + if let error = error { + print(error, to: &errorStream) + status = 2 + } + else if let identity = identity { + printIdentity(identity: identity) + } + semaphore.signal() + } + print("Waiting...", to: &debugStream) + semaphore.wait() + print("Done", to: &debugStream) + request.close() + + default: + usage() + status = 1 + } + return status +} + +main() diff --git a/swift-client-api/package/Package.swift b/swift-client-api/package/Package.swift new file mode 100644 index 00000000..76990a1c --- /dev/null +++ b/swift-client-api/package/Package.swift @@ -0,0 +1,7 @@ +import PackageDescription + +let package = Package( + name: "ServalClient" + ) + +products.append(Product(name: "ServalClient", type: .Library(.Static), modules: "ServalClient")) diff --git a/swift-client-api/package/Sources/abstract_id.swift b/swift-client-api/package/Sources/abstract_id.swift new file mode 100644 index 00000000..b7c42a86 --- /dev/null +++ b/swift-client-api/package/Sources/abstract_id.swift @@ -0,0 +1,109 @@ +/* +Serval DNA Swift API +Copyright (C) 2016 Flinders University + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import Foundation + +/* All the Serval Id types (SID, BundleId, etc.) are binary blobs that are +* typically represented in hexadecimal text format. Some also have an +* "abbreviated" format. +*/ + +public protocol AbstractId : Hashable, CustomStringConvertible { + init(fromBinary: [UInt8]) + init?(fromHex: String) + var binary : [UInt8] { get } + var hexUpper : String { get } +} + +public protocol AbbreviatedId : AbstractId { + var abbreviation : String { get } +} + +/* Implementations of AbstractId that specialise GenericIdImplementation must +* also implement the ConcreteId protocol, which allows users to discover +* properties of the specialised type without instantiating it. +*/ + +public protocol ConcreteId { + static var byteCount : Int { get } + static var mimeType : String { get } +} + +/* All Serval Id types are specialisations of this generic implementation, +* which stores the Id's value as an array of bytes, and derives all other +* properties from that representation. +*/ + +public class GenericIdImplementation : AbstractId { + public let binary : [UInt8] + + public static func == (lhs: GenericIdImplementation, rhs: GenericIdImplementation) -> Bool { + return lhs.binary == rhs.binary + } + + public var hashValue : Int { + return self.binary.map { $0.hashValue }.reduce(0) { + (($0 << 8) | Int(UInt($0) >> UInt((MemoryLayout.size - 1) * 8))) ^ $1 + } + } + + internal init(fill: UInt8) { + self.binary = Array(repeating: fill, count: (type(of:self) as! ConcreteId.Type).byteCount) + } + + public required init(fromBinary binary: [UInt8]) { + self.binary = binary + assert(self.binary.count == (type(of:self) as! ConcreteId.Type).byteCount) + } + + public required init?(fromHex hex: String) { + var binary = Array(repeating: UInt8(0), count: (type(of:self) as! ConcreteId.Type).byteCount) + var index = hex.startIndex + for i in 0 ..< binary.count { + guard let next = hex.index(index, offsetBy: 2, limitedBy: hex.endIndex) else { + return nil + } + let digits = hex.substring(with: index ..< next) + guard let byte = UInt8(digits, radix: 16) else { + return nil + } + binary[i] = byte + index = next + } + guard index == hex.endIndex else { + return nil + } + self.binary = binary + } + + public var hexUpper : String { + return self.binary.map { String(format: "%02hhX", $0) }.joined() + } + + internal func hexUpper(digitCount: Int) -> String { + let byteCount = (digitCount + 1) / 2 + assert(byteCount <= self.binary.count) + let hex = self.binary[0 ..< byteCount].map { String(format: "%02hhX", $0) }.joined() + return digitCount == byteCount * 2 ? hex : hex.substring(with: hex.startIndex ..< hex.index(hex.endIndex, offsetBy: -1)) + } + + public var description : String { + return "\(type(of:self))(fromHex: \"\(self.hexUpper)\")" + } +} diff --git a/swift-client-api/package/Sources/debug.swift b/swift-client-api/package/Sources/debug.swift new file mode 100644 index 00000000..63ceec36 --- /dev/null +++ b/swift-client-api/package/Sources/debug.swift @@ -0,0 +1,31 @@ +/* +Serval DNA Swift API +Copyright (C) 2017 Flinders University + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#if os(Linux) + import Glibc +#else + import Darwin +#endif + +public struct ErrorOutputStream: TextOutputStream { + public mutating func write(_ string: String) { fputs(string, stderr) } +} + +public var errorStream = ErrorOutputStream() +public var debugStream = errorStream diff --git a/swift-client-api/package/Sources/keyring.swift b/swift-client-api/package/Sources/keyring.swift new file mode 100644 index 00000000..87c786cb --- /dev/null +++ b/swift-client-api/package/Sources/keyring.swift @@ -0,0 +1,240 @@ +/* +Serval DNA Swift API +Copyright (C) 2016 Flinders University + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import Foundation + +public class ServalKeyring { + + public struct Identity { + public let sid : SubscriberId + public let identity : SubscriberId + public let did : String? + public let name : String? + } + + public static func listIdentities(client: ServalRestfulClient = ServalRestfulClient(), + pin: String? = nil, + completionHandler: @escaping ([Identity]?, Error?) -> Void) + -> ServalRestfulClient.Request + { + var param = [String: String]() + if pin != nil { param["pin"] = pin } + return client.createRequest(path: "restful/keyring/identities.json", + query: param) { (statusCode, json, error) in + if let error = error { + completionHandler(nil, error) + return + } + guard statusCode! == 200 else { + completionHandler(nil, ServalRestfulClient.Exception.requestFailed(statusCode: statusCode!)) + return + } + guard let json_top = json as? [String: Any] else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "root is not JSON object")) + return + } + var column_count = 0 + var sid_index = -1 + var identity_index = -1 + var did_index = -1 + var name_index = -1 + guard let header = json_top["header"] as? [String] else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "missing 'header' element")) + return + } + for text in header { + if text == "sid" { + sid_index = column_count + } + else if text == "identity" { + identity_index = column_count + } + else if text == "did" { + did_index = column_count + } + else if text == "name" { + name_index = column_count + } + column_count += 1 + } + guard sid_index != -1 else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "missing 'sid' column")) + return + } + guard identity_index != -1 else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "missing 'identity' column")) + return + } + guard let rows = json_top["rows"] as? [[Any]] else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "missing 'rows' element")) + return + } + var identities : [Identity] = [] + for row in rows { + guard row.count == column_count else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "row has \(row.count) elements; should be \(column_count)")) + return + } + var opt_sid : SubscriberId? + var opt_identity : SubscriberId? + var did : String? + var name : String? + if sid_index != -1 { + guard let hex = row[sid_index] as? String else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "sid value is not String")) + return + } + opt_sid = SubscriberId(fromHex: hex) + guard opt_sid != nil else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "sid value is not hex: \(hex)")) + return + } + } + if identity_index != -1 { + guard let hex = row[identity_index] as? String else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "identity value is not String")) + return + } + opt_identity = SubscriberId(fromHex: hex) + guard opt_sid != nil else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "identity value is not hex: \(hex)")) + return + } + } + if did_index != -1 { + let value = row[did_index] + if value as? NSNull == nil { + guard let text = value as? String else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "did value is not String: \(value)")) + return + } + if !text.isEmpty { + did = text + } + } + } + if name_index != -1 { + let value = row[name_index] + if value as? NSNull == nil { + guard let text = value as? String else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "name value is not String: \(value)")) + return + } + if !text.isEmpty { + name = text + } + } + } + identities.append(Identity(sid: opt_sid!, identity: opt_identity!, did: did, name: name)) + } + completionHandler(identities, nil) + }! + } + + private static func singleIdentityRequest(client: ServalRestfulClient = ServalRestfulClient(), + path: String, + query: [String: String] = [:], + successStatusCodes: Set = [200], + completionHandler: @escaping (Identity?, Error?) -> Void) + -> ServalRestfulClient.Request + { + return client.createRequest(path: path, query: query) { (statusCode, json, error) in + if let error = error { + completionHandler(nil, error) + return + } + guard successStatusCodes.contains(statusCode!) else { + completionHandler(nil, ServalRestfulClient.Exception.requestFailed(statusCode: statusCode!)) + return + } + guard let json_top = json as? [String: Any] else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "root is not JSON object")) + return + } + guard let json_identity = json_top["identity"] as? [String: Any] else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "'identity' is not JSON object")) + return + } + guard let sid_hex = json_identity["sid"] as? String else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "'sid' is not String")) + return + } + guard let identity_hex = json_identity["identity"] as? String else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "'identity' is not String")) + return + } + guard let sid = SubscriberId(fromHex: sid_hex) else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "invalid 'sid': \(sid_hex)")) + return + } + guard let identity = SubscriberId(fromHex: identity_hex) else { + completionHandler(nil, ServalRestfulClient.Exception.invalidJson(reason: "invalid 'identity': \(identity_hex)")) + return + } + let did = json_identity["did"] as? String ?? "" + let name = json_identity["name"] as? String ?? "" + completionHandler(Identity(sid: sid, identity: identity, did: did, name: name), nil) + }! + } + + public static func addIdentity(client: ServalRestfulClient = ServalRestfulClient(), + did: String? = nil, + name: String? = nil, + pin: String? = nil, + completionHandler: @escaping (Identity?, Error?) -> Void) + -> ServalRestfulClient.Request + { + var param = [String: String]() + if did != nil { param["did"] = did } + if name != nil { param["name"] = name } + if pin != nil { param["pin"] = pin } + return self.singleIdentityRequest(client: client, + path: "restful/keyring/add", + query: param, + successStatusCodes: [201], + completionHandler: completionHandler) + } + + public static func removeIdentity(client: ServalRestfulClient = ServalRestfulClient(), + sid: SubscriberId, + pin: String? = nil, + completionHandler: @escaping (Identity?, Error?) -> Void) + -> ServalRestfulClient.Request + { + var param = [String: String]() + if pin != nil { param["pin"] = pin } + return self.singleIdentityRequest(client: client, path: "restful/keyring/\(sid.hexUpper)/remove", query: param, completionHandler: completionHandler) + } + + public static func setIdentity(client: ServalRestfulClient = ServalRestfulClient(), + sid: SubscriberId, + did: String? = nil, + name: String? = nil, + pin: String? = nil, + completionHandler: @escaping (Identity?, Error?) -> Void) + -> ServalRestfulClient.Request + { + var param = [String: String]() + if did != nil { param["did"] = did } + if name != nil { param["name"] = name } + if pin != nil { param["pin"] = pin } + return self.singleIdentityRequest(client: client, path: "restful/keyring/\(sid.hexUpper)/set", query: param, completionHandler: completionHandler) + } + +} diff --git a/swift-client-api/package/Sources/restful_client.swift b/swift-client-api/package/Sources/restful_client.swift new file mode 100644 index 00000000..476060fb --- /dev/null +++ b/swift-client-api/package/Sources/restful_client.swift @@ -0,0 +1,119 @@ +/* +Serval DNA Swift API +Copyright (C) 2016 Flinders University + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import Foundation + +public class ServalRestfulClient { + + public struct Configuration { + public let host : String + public let port : Int16 + public let username : String + public let password : String + + public func with(host: String? = nil, port: Int16? = nil, username: String? = nil, password: String? = nil) -> Configuration { + return Configuration(host: host ?? type(of:self).default.host, + port: port ?? type(of:self).default.port, + username: username ?? type(of:self).default.username, + password: password ?? type(of:self).default.password) + } + + public static let `default` = Configuration( + host: "127.0.0.1", + port : 4110, + username: "", + password: "" + ) + } + + public let configuration : Configuration + + public enum Exception : Error { + case requestFailed(statusCode: Int) + case missingContentType + case invalidContentType(mimeType: String) + case malformedJson + case invalidJson(reason: String) + } + + public struct Request { + fileprivate let urlSession : URLSession + fileprivate let dataTask : URLSessionDataTask + public func close() { + print("TODO: close", to: &debugStream) // TODO + } + } + + public init(configuration: Configuration = Configuration.default) { + self.configuration = configuration + } + + public func createRequest(path: String, + query: [String: String] = [:], + completionHandler: @escaping (Int?, Any?, Error?) -> Void) + -> Request? + { + var urlComponents = URLComponents() + urlComponents.scheme = "http" + urlComponents.host = self.configuration.host + urlComponents.port = Int(self.configuration.port) + urlComponents.path = "/\(path)" + var items : [URLQueryItem] = [] + for (name, value) in query { + items.append(URLQueryItem(name: name, value: value)) + } + urlComponents.queryItems = items + if !query.isEmpty { + precondition(urlComponents.percentEncodedQuery != nil) + } + let url = urlComponents.url! + let session = URLSession(configuration: URLSessionConfiguration.default) + debugPrint(url, to: &debugStream) + var request = URLRequest(url: url) + request.httpMethod = "GET" + if !self.configuration.username.isEmpty { + let data = (self.configuration.username + ":" + self.configuration.password).data(using: String.Encoding.utf8) + if let base64 = data?.base64EncodedString() { + request.setValue("Basic \(base64)", forHTTPHeaderField: "Authorization") + } + } + let dataTask = session.dataTask(with: request) { (data, response, error) in + if let error = error { + completionHandler(nil, nil, error) + return + } + let httpResponse = response as! HTTPURLResponse + guard let mimeType = httpResponse.mimeType else { + completionHandler(nil, nil, ServalRestfulClient.Exception.missingContentType) + return + } + guard mimeType == "application/json" else { + completionHandler(nil, nil, ServalRestfulClient.Exception.invalidContentType(mimeType: mimeType)) + return + } + guard let json = try? JSONSerialization.jsonObject(with: data!, options: []) else { + completionHandler(nil, nil, ServalRestfulClient.Exception.malformedJson) + return + } + completionHandler(httpResponse.statusCode, json, nil) + } + dataTask.resume() + return Request(urlSession: session, dataTask: dataTask) + } +} diff --git a/swift-client-api/package/Sources/subscriber_id.swift b/swift-client-api/package/Sources/subscriber_id.swift new file mode 100644 index 00000000..52f62375 --- /dev/null +++ b/swift-client-api/package/Sources/subscriber_id.swift @@ -0,0 +1,32 @@ +/* +Serval DNA Swift API +Copyright (C) 2016 Flinders University + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +public class SubscriberId : GenericIdImplementation, ConcreteId, AbbreviatedId { + public static let byteCount = 32 + + public static let mimeType = "serval-mesh/sid" + + public var abbreviation : String { + return "sid:\(self.hexUpper(digitCount: 6))*" + } + + public static let any = SubscriberId(fill: 0) + + public static let broadcast = SubscriberId(fill: 0xff) +} diff --git a/testconfig.sh.in b/testconfig.sh.in index 29e5917a..cdcca51c 100644 --- a/testconfig.sh.in +++ b/testconfig.sh.in @@ -2,3 +2,4 @@ # This file is sourced by some of the test scripts in ./tests. # It defines some settings that were established by ./configure. JAVAC="@JAVAC@" +SWIFTC="@SWIFTC@" diff --git a/testdefs.sh b/testdefs.sh index 8345c11a..37bf2553 100644 --- a/testdefs.sh +++ b/testdefs.sh @@ -83,7 +83,7 @@ extract_stdout_keyvalue_optional() { esac local _label_re=$(escape_grep_basic "$_label") local _delim_re=$(escape_grep_basic "$_delim") - local _line=$($GREP "^$_label_re$_delim_re" "$TFWSTDOUT") + local _line=$($GREP "^$_label_re$_delim_re$_rexp" "$TFWSTDOUT") local _value= local _return=1 if [ -n "$_line" ]; then @@ -104,6 +104,16 @@ extract_stdout_keyvalue() { assert --message="stdout of ($TFWEXECUTED) contains valid '$_label:' line" --stdout extract_stdout_keyvalue_optional "$@" } +# Assert function for the output of servald commands that return "key:value\n" +# pairs. +assert_stdout_keyvalue() { + local _label="$1" + local _value="$2" + local _extracted + extract_stdout_keyvalue _extracted "$_label" '.*' + assert --message="stdout of ($executed) '$_label:' line does not equal '$_value'" --stdout test "$_extracted" = "$_value" +} + # Parse the standard result set output produced by the immediately preceding command # command into the following shell variables: # NCOLS the number of columns diff --git a/testdefs_swift.sh b/testdefs_swift.sh new file mode 100644 index 00000000..551774d1 --- /dev/null +++ b/testdefs_swift.sh @@ -0,0 +1,34 @@ +# Definitions for test suites using Swift +# Copyright 2017 Flinders University +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +swift_client_util="$servald_build_root/swift-client-api/swift-client-util" + +SWIFT_TEST_USER="steve" +SWIFT_TEST_PASSWORD="jobs" + +assert_swift_executable_exists() { + executeSwiftOk help +} + +executeSwiftOk() { + executeOk --stdout --stderr --core-backtrace \ + --executable="$swift_client_util" \ + -- \ + --port "${SWIFT_TEST_PORT?}" \ + ${SWIFT_TEST_USER:+--user "$SWIFT_TEST_USER" --password "$SWIFT_TEST_PASSWORD"} \ + "$@" +} diff --git a/tests/all b/tests/all index 3366b4ea..fbe1767f 100755 --- a/tests/all +++ b/tests/all @@ -3,6 +3,7 @@ # Aggregation of all tests except high-load stress tests. # # Copyright 2012-2014 Serval Project Inc. +# Copyright 2017 Flinders University # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -43,5 +44,8 @@ includeTests meshmbrestful if type -p "$JAVAC" >/dev/null; then includeTests alljava fi +if type -p "$SWIFTC" >/dev/null; then + includeTests keyringswift +fi runTests "$@" diff --git a/tests/keyringswift b/tests/keyringswift new file mode 100755 index 00000000..96d3f29b --- /dev/null +++ b/tests/keyringswift @@ -0,0 +1,155 @@ +#!/bin/bash + +# Tests for Keyring Swift API. +# +# Copyright 2015 Serval Project Inc. +# Copyright 2017 Flinders University +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +source "${0%/*}/../testframework.sh" +source "${0%/*}/../testdefs.sh" +source "${0%/*}/../testdefs_swift.sh" + +setup() { + setup_servald + set_instance +A + set_keyring_config + set_extra_config + executeOk_servald config \ + set "api.restful.users.$SWIFT_TEST_USER.password" "$SWIFT_TEST_PASSWORD" + if [ -z "$IDENTITY_COUNT" ]; then + create_single_identity + else + create_identities $IDENTITY_COUNT + fi + export SERVALD_RHIZOME_DB_RETRY_LIMIT_MS=60000 + start_servald_server + wait_until servald_restful_http_server_started +A + get_servald_restful_http_server_port SWIFT_TEST_PORT +A +} + +teardown() { + stop_all_servald_servers + kill_all_servald_processes + assert_no_servald_processes + report_all_servald_servers +} + +set_keyring_config() { + executeOk_servald config \ + set log.console.level debug \ + set debug.httpd on \ + set debug.keyring on \ + set debug.verbose on +} + +set_extra_config() { + : +} + +doc_keyringList="Swift API list keyring identities" +setup_keyringList() { + IDENTITY_COUNT=10 + DIDA1=123123123 + NAMEA1='Joe Bloggs' + DIDA5=567567567 + setup +} +test_keyringList() { + executeSwiftOk keyring list + tfw_cat --stdout --stderr + assert_keyring_list $IDENTITY_COUNT +} + +doc_keyringListPin="Swift API list keyring identities, with PIN" +setup_keyringListPin() { + IDENTITY_COUNT=3 + PINA1='wif waf' + setup +} +test_keyringListPin() { + # First, list without supplying the PIN + executeSwiftOk keyring list + tfw_cat --stdout --stderr + assert_keyring_list $((IDENTITY_COUNT - 1)) + # Then, list supplying the PIN + executeSwiftOk keyring list --pin "$PINA1" + tfw_cat --stdout --stderr + assert_keyring_list $IDENTITY_COUNT +} + +doc_keyringAddDidName="Swift API add new keyring identity" +setup_keyringAddDidName() { + IDENTITY_COUNT=1 + DIDA2=123123123 + NAMEA2='Joe Bloggs' + setup +} +test_keyringAddDidName() { + executeSwiftOk keyring add did "$DIDA2" name "$NAMEA2" + tfw_cat --stdout --stderr + assert_stdout_keyvalue did "$DIDA2" + assert_stdout_keyvalue name "$NAMEA2" + extract_stdout_keyvalue SID sid "$rexp_sid" + extract_stdout_keyvalue ID identity "$rexp_id" + executeOk_servald keyring list + assert_keyring_list 2 + assertStdoutGrep --stderr --matches=1 "^$SID:$ID:$DIDA2:$NAMEA2\$" +} + +doc_keyringRemove="Swift API remove keyring identity" +setup_keyringRemove() { + IDENTITY_COUNT=2 + setup +} +test_keyringRemove() { + executeSwiftOk keyring remove "$SIDA1" + assert_stdout_keyvalue sid "$SIDA1" + executeOk_servald keyring list + assert_keyring_list 1 + assertStdoutGrep --matches=0 "^$SIDA1:" + assertStdoutGrep --matches=1 "^$SIDA2:" + executeSwiftOk keyring remove "$SIDA2" + assert_stdout_keyvalue sid "$SIDA2" + executeOk_servald keyring list + assert_keyring_list 0 +} + +doc_keyringSetDidName="Swift API set DID and Name of keyring identity" +setup_keyringSetDidName() { + IDENTITY_COUNT=1 + setup +} +test_keyringSetDidName() { + executeSwiftOk keyring set "$SIDA1" did '123456' + assert_stdout_keyvalue sid "$SIDA1" + assert_stdout_keyvalue identity "$IDA1" + assert_stdout_keyvalue did "123456" + assert_stdout_keyvalue name "" + executeOk_servald keyring list + assert_keyring_list 1 + assertStdoutGrep --stderr --matches=1 "^$SIDA1:$IDA1:123456:\$" + executeSwiftOk keyring set "$SIDA1" name 'Display Name' + assert_stdout_keyvalue sid "$SIDA1" + assert_stdout_keyvalue identity "$IDA1" + assert_stdout_keyvalue did "123456" + assert_stdout_keyvalue name "Display Name" + executeOk_servald keyring list + assert_keyring_list 1 + assertStdoutGrep --stderr --matches=1 "^$SIDA1:$IDA1:123456:Display Name\$" +} + +runTests "$@"