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.
This commit is contained in:
Andrew Bettison 2016-10-25 16:16:08 +10:30
parent f2eb2bf9ab
commit 3ffa4b10af
24 changed files with 1453 additions and 29 deletions

4
.gitignore vendored
View File

@ -7,6 +7,7 @@
/objs /objs
/objs_lib /objs_lib
/objs_servald /objs_servald
*.o
*.a *.a
*.suo *.suo
.*.sw? .*.sw?
@ -36,3 +37,6 @@ test.*.log
/java-api/Makefile /java-api/Makefile
/java-api/classes/ /java-api/classes/
/java-api/testclasses/ /java-api/testclasses/
/swift-client-api/Makefile
/swift-client-api/build/
/swift-client-api/swift-client-util

View File

@ -150,6 +150,14 @@ A successful session should appear something like:
Note: Some input files use or override a deprecated API. Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details. Note: Recompile with -Xlint:deprecation for details.
make[1]: Leaving directory '/home/username/src/serval-dna/java-api' 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 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 Swift language's run-time support and resultant library dependencies, so is
not suitable for deployment. 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 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/ [gcc 6]: http://gcc.gnu.org/gcc-6/
[Notes for Developers]: ./doc/Development.md [Notes for Developers]: ./doc/Development.md
[Swift development]: ./doc/Development.md#swift [Swift development]: ./doc/Development.md#swift
[Swift Client API module]: ./doc/Development.md#swift-client-api
[OpenWRT]: ./doc/OpenWRT.md [OpenWRT]: ./doc/OpenWRT.md
[Serval Infrastructure]: ./doc/Serval-Infrastructure.md [Serval Infrastructure]: ./doc/Serval-Infrastructure.md
[Serval Mesh Extender]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:meshextender: [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 [CLI API]: ./doc/CLI-API.md
[JNI]: http://en.wikipedia.org/wiki/Java_Native_Interface [JNI]: http://en.wikipedia.org/wiki/Java_Native_Interface
[Swift]: https://en.wikipedia.org/wiki/Swift_(programming_language) [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) [Bash]: http://en.wikipedia.org/wiki/Bash_(Unix_shell)
[GNU make]: http://www.gnu.org/software/make/ [GNU make]: http://www.gnu.org/software/make/
[Git]: http://git-scm.com/ [Git]: http://git-scm.com/

View File

@ -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 # vim: noet ts=8 sts=0 sw=8
prefix=@prefix@ prefix=@prefix@
exec_prefix=@exec_prefix@ exec_prefix=@exec_prefix@
@ -88,6 +88,7 @@ CFLAGS+=-DSQLITE_THREADSAFE=0 \
-DSQLITE_OMIT_VIRTUALTABLE \ -DSQLITE_OMIT_VIRTUALTABLE \
-DSQLITE_OMIT_AUTHORIZATION -DSQLITE_OMIT_AUTHORIZATION
CFLAGS+=-fPIC -DSERVAL_ENABLE_DEBUG=1 -Wall -Werror -Wextra -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 CFLAGS+=-fPIC -DSERVAL_ENABLE_DEBUG=1 -Wall -Werror -Wextra -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2
CFLAGS+=@CFLAGS_TARGET@
# Solaris magic # Solaris magic
CFLAGS+=-DSHA2_USE_INTTYPES_H -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED=1 -D__EXTENSIONS__=1 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) 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' # Build the Sodium elliptic curve encryption library within its 'libsodium'
# subtree, and install its development files (headers and libraries) into our # subtree, and install its development files (headers and libraries) into our
# $(LIBSODIUM_DEV) subdirectory. Then use the contents of this subdirectory to # $(LIBSODIUM_DEV) subdirectory. Then use the contents of this subdirectory to
@ -186,7 +206,11 @@ $(LIBSODIUM_DEV)/.installed:
@mkdir -p $(LIBSODIUM_DEV) @mkdir -p $(LIBSODIUM_DEV)
@$(RM) $@ @$(RM) $@
@touch $@.in-progress @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 $@ @mv -f $@.in-progress $@
# Source code test coverage support -- see doc/Testing.md # 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. 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 # 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 # 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 # mainly to ensure that the Serval daemon library can be linked into a Swift

View File

@ -6,11 +6,11 @@ AC_CONFIG_MACRO_DIR([m4])
AC_ARG_VAR([AR], [Library archiver]) AC_ARG_VAR([AR], [Library archiver])
AC_ARG_VAR([RANLIB], [Archive indexer]) AC_ARG_VAR([RANLIB], [Archive indexer])
dnl Specify Swift compiler
AC_ARG_VAR([SWIFTC], [Swift compiler]) AC_ARG_VAR([SWIFTC], [Swift compiler])
AC_ARG_VAR([SWIFTCFLAGS], [Swift compiler flags]) AC_ARG_VAR([SWIFTCFLAGS], [Swift compiler flags])
CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
dnl Specify default instance path dnl Specify default instance path
AC_ARG_VAR([INSTANCE_PATH], [default instance path for servald]) 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])]) 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]) 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])]) 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. dnl Set $build, $build_cpu, $build_vendor, $build_os to reflect the native
AC_CANONICAL_SYSTEM 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_CC_C99
AC_PROG_CPP AC_PROG_CPP
AM_PROG_AS
dnl Check for library creation tools.
AC_CHECK_TOOL([AR], [ar], [:]) AC_CHECK_TOOL([AR], [ar], [:])
AS_IF([test "x$AR" = x:], [AC_MSG_ERROR([Library archiver not found: ar])]) AS_IF([test "x$AR" = x:], [AC_MSG_ERROR([Library archiver not found: ar])])
AC_CHECK_TOOL([RANLIB], [ranlib], [:]) AC_CHECK_TOOL([RANLIB], [ranlib], [:])
AS_IF([test "x$RANLIB" = x:], [AC_MSG_ERROR([Archive indexer not found: 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. dnl Check for a Swift 3 or 4 compiler; set SWIFTC if found.
AC_PROG_SWIFTC AC_PROG_SWIFTC
AS_IF([test "x$SWIFTC" != x], [ dnl 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 Check whether the Swift compiler will compile a simple Swift 4 program with the supplied flags.
dnl If not, report failure but keep going. dnl If not, report failure but keep going.
AC_PROG_SWIFTC_IS_SWIFT4 AC_PROG_SWIFTC_IS_SWIFT4
@ -67,11 +105,31 @@ AS_IF([test "x$SWIFTC" != x], [ dnl
SWIFTC= 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([SWIFTC])
AC_SUBST([SWIFTCFLAGS]) AC_SUBST([SWIFTCFLAGS])
AC_SUBST([SWIFT_VERSION]) AC_SUBST([SWIFT_VERSION])
AC_SUBST([SWIFT_BUILD])
dnl Various GCC function and variable attributes dnl Various GCC function and variable attributes
AX_GCC_FUNC_ATTRIBUTE(aligned) 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. dnl See doc/Development.md for details.
AX_APPEND_LINK_FLAGS(-fuse-ld=gold) AX_APPEND_LINK_FLAGS(-fuse-ld=gold)
dnl Check compiler options for compiling SQLite. dnl Early versions of GCC on Mac OS-X would fail without this flag, which soon
AX_APPEND_COMPILE_FLAGS( 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-empty-body \
-Wno-unused-value \ -Wno-unused-value \
-Wno-unused-function \ -Wno-unused-function \
@ -99,8 +165,12 @@ AX_APPEND_COMPILE_FLAGS(
-Wno-unused-variable \ -Wno-unused-variable \
-Wno-unused-but-set-variable \ -Wno-unused-but-set-variable \
-Wno-missing-field-initializers \ -Wno-missing-field-initializers \
-Wno-deprecated-declarations, -Wno-deprecated-declarations \
[CFLAGS_SQLITE], [-Werror]) '-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]) AC_SUBST([CFLAGS_SQLITE])
dnl Math library functions for spandsp 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]) AC_CHECK_SIZEOF([off_t])
dnl There must be a 64-bit seek(2) system call of some kind 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]) 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]) 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. dnl Check if the Linux gettid() and tgkill() system calls are supported.
AC_CHECK_FUNCS([gettid tgkill]) AC_CHECK_FUNCS([gettid tgkill])
AC_CACHE_CHECK([Linux thread system calls], ac_cv_have_linux_threads, [ 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_CONFIG_HEADERS([config.h])
AC_SUBST([CONFIG_H], [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([ AC_OUTPUT([
Makefile Makefile
testconfig.sh testconfig.sh
java-api/Makefile java-api/Makefile
swift-client-api/Makefile
]) ])

View File

@ -159,9 +159,9 @@ Apple Mac OS X
### Test utilities ### Test utilities
The [OS X grep(1)][] , [OS X sed(1)][] and [OS X awk(1)][] tools provided by 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 Apple Mac OS X are the BSD variants. The [test scripts][] require the GNU
with the names *ggrep*, *gsed* and *gawk*, which can be installed on Mac OS X variants with the names *ggrep*, *gsed* and *gawk*, which can be installed on
using the [homebrew][] package manager: Mac OS X using the [homebrew][] package manager:
$ brew tap homebrew/dupes $ brew tap homebrew/dupes
==> Tapping 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; * `SWIFTCFLAGS` extra command-line arguments to pass to the Swift compiler;
analogous to `CFLAGS` for the C 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 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 [CC BY 4.0]: ../LICENSE-DOCUMENTATION.md
[Serval DNA]: ../README.md [Serval DNA]: ../README.md
[build]: ../INSTALL.md [build]: ../INSTALL.md
[REST API]: ./REST-API.md
[Keyring REST API]: ./REST-API-Keyring.md
[test scripts]: ./Testing.md
[configure.ac]: ../configure.ac [configure.ac]: ../configure.ac
[autoconf]: http://www.gnu.org/software/autoconf/autoconf.html [autoconf]: http://www.gnu.org/software/autoconf/autoconf.html
[autoconf macro archive]: http://www.gnu.org/software/autoconf-archive/ [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 module]: https://swift.org/package-manager/#modules
[Swift 3]: https://swift.org/blog/swift-3-0-released/ [Swift 3]: https://swift.org/blog/swift-3-0-released/
[Swift 4]: https://swift.org/blog/swift-4-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) [gold]: https://en.wikipedia.org/wiki/Gold_(linker)
[Bourne shell]: http://en.wikipedia.org/wiki/Bourne_shell [Bourne shell]: http://en.wikipedia.org/wiki/Bourne_shell

View File

@ -210,6 +210,10 @@ recognised:
* **name**: the name; empty to clear the name, otherwise must conform to the * **name**: the name; empty to clear the name, otherwise must conform to the
rules for [Name](#name) 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 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][400]. If there is no unlocked identity with the given SID, this
request returns [404 Not Found][404]. request returns [404 Not Found][404].

View File

@ -1612,6 +1612,10 @@ int keyring_commit(keyring_file *k)
int keyring_set_did_name(keyring_identity *id, const char *did, const char *name) 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 */ /* Find where to put it */
keypair *kp = id->keypairs; keypair *kp = id->keypairs;
while(kp){ while(kp){
@ -1628,26 +1632,28 @@ int keyring_set_did_name(keyring_identity *id, const char *did, const char *name
return -1; return -1;
keyring_identity_add_keypair(id, kp); keyring_identity_add_keypair(id, kp);
DEBUG(keyring, "Created DID/Name record for identity"); 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. */ /* Store DID as nul-terminated string. */
{ if (did) {
size_t len = strlen(did); size_t len = strlen(did);
assert(len < kp->private_key_len); assert(len < kp->private_key_len);
bcopy(did, &kp->private_key[0], len); bcopy(did, &kp->private_key[0], len);
bzero(&kp->private_key[len], kp->private_key_len - 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. */ /* Store Name as nul-terminated string. */
{ if (name) {
size_t len = strlen(name); size_t len = strlen(name);
assert(len < kp->public_key_len); assert(len < kp->public_key_len);
bcopy(name, &kp->public_key[0], len); bcopy(name, &kp->public_key[0], len);
bzero(&kp->public_key[len], kp->public_key_len - 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; return 0;
} }

View File

@ -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); keyring_identity *id = keyring_find_identity_sid(keyring, &r->sid1);
if (!id) if (!id)
return http_request_keyring_response(r, 404, "Identity not found"); 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"); return http_request_keyring_response(r, 500, "Could not set identity DID/Name");
if (keyring_commit(keyring) == -1) if (keyring_commit(keyring) == -1)
return http_request_keyring_response(r, 500, "Could not store new identity"); return http_request_keyring_response(r, 500, "Could not store new identity");

View File

@ -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 <http://www.gnu.org/licenses/>.
#
# 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
])

View File

@ -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 <http://www.gnu.org/licenses/>.
#
# 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
])

View File

@ -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 <http://www.gnu.org/licenses/>.
#
# 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
])

View File

@ -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

View File

@ -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<Int>
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()

View File

@ -0,0 +1,7 @@
import PackageDescription
let package = Package(
name: "ServalClient"
)
products.append(Product(name: "ServalClient", type: .Library(.Static), modules: "ServalClient"))

View File

@ -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<Int>.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)\")"
}
}

View File

@ -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

View File

@ -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<Int> = [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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -2,3 +2,4 @@
# This file is sourced by some of the test scripts in ./tests. # This file is sourced by some of the test scripts in ./tests.
# It defines some settings that were established by ./configure. # It defines some settings that were established by ./configure.
JAVAC="@JAVAC@" JAVAC="@JAVAC@"
SWIFTC="@SWIFTC@"

View File

@ -83,7 +83,7 @@ extract_stdout_keyvalue_optional() {
esac esac
local _label_re=$(escape_grep_basic "$_label") local _label_re=$(escape_grep_basic "$_label")
local _delim_re=$(escape_grep_basic "$_delim") 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 _value=
local _return=1 local _return=1
if [ -n "$_line" ]; then 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 --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 # Parse the standard result set output produced by the immediately preceding command
# command into the following shell variables: # command into the following shell variables:
# NCOLS the number of columns # NCOLS the number of columns

34
testdefs_swift.sh Normal file
View File

@ -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"} \
"$@"
}

View File

@ -3,6 +3,7 @@
# Aggregation of all tests except high-load stress tests. # Aggregation of all tests except high-load stress tests.
# #
# Copyright 2012-2014 Serval Project Inc. # Copyright 2012-2014 Serval Project Inc.
# Copyright 2017 Flinders University
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -43,5 +44,8 @@ includeTests meshmbrestful
if type -p "$JAVAC" >/dev/null; then if type -p "$JAVAC" >/dev/null; then
includeTests alljava includeTests alljava
fi fi
if type -p "$SWIFTC" >/dev/null; then
includeTests keyringswift
fi
runTests "$@" runTests "$@"

155
tests/keyringswift Executable file
View File

@ -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 "$@"