summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/nns
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/nns
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/modules/nns')
-rw-r--r--tcllib/modules/nns/ChangeLog192
-rw-r--r--tcllib/modules/nns/common.tcl38
-rw-r--r--tcllib/modules/nns/common.test34
-rw-r--r--tcllib/modules/nns/nns.tcl432
-rw-r--r--tcllib/modules/nns/nns_auto.man119
-rw-r--r--tcllib/modules/nns/nns_auto.tcl443
-rw-r--r--tcllib/modules/nns/nns_client.man338
-rw-r--r--tcllib/modules/nns/nns_cluster.tcl499
-rw-r--r--tcllib/modules/nns/nns_cluster.test195
-rw-r--r--tcllib/modules/nns/nns_common.man47
-rw-r--r--tcllib/modules/nns/nns_intro.man128
-rw-r--r--tcllib/modules/nns/nns_protocol.man182
-rw-r--r--tcllib/modules/nns/nns_server.man145
-rw-r--r--tcllib/modules/nns/pkgIndex.tcl10
-rw-r--r--tcllib/modules/nns/server.tcl385
15 files changed, 3187 insertions, 0 deletions
diff --git a/tcllib/modules/nns/ChangeLog b/tcllib/modules/nns/ChangeLog
new file mode 100644
index 0000000..75de1b2
--- /dev/null
+++ b/tcllib/modules/nns/ChangeLog
@@ -0,0 +1,192 @@
+2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.15 ========================
+ *
+
+2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.14 ========================
+ *
+
+2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.13 ========================
+ *
+
+2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.12 ========================
+ *
+
+2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11.1 ========================
+ *
+
+2008-10-21 Andreas Kupries <andreask@activestate.com>
+
+ * nns.tcl: Fixed [Bug 2182378], reported by Joe Brandt
+ * pkgIndex.tcl: <vonbane@users.sourceforge.net>. Added
+ * nns_client.man: the missing assignment for 'oneshot'.
+ Version bumped to 0.4.2.
+
+2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11 ========================
+ *
+
+2008-08-13 Michael Schlenker <mic42@users.sourceforge.net>
+
+ * nns.tcl: Fixed missing variable. Bumped version to 0.4.1.
+ * nns_auto.tcl:
+ * pkgIndex.tcl:
+
+2008-05-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ../../apps/nns: Switched to use of nameserv::auto to handle the
+ * ../../apps/nnslog: loss and restoration of the server
+ automatically. Got rid of the colorization frills.
+
+ * server.tcl (::nameserv::server::bind): Small extension of log
+ * pkgIndex.tcl: output for failure case of bind. Added log output
+ * nns_server.man: to trace searches. Bumped package version to 0.3.2.
+
+ * nns_auto.tcl: Refactored the bind and restore code, put the
+ * nns_auto.man: commonalities into shared commands. Extended the
+ * pkgIndex.tcl: API to become a full drop-in replacement for
+ 'nameserv', just with the persistence feature. Extended the
+ persistence to continuous and unfulfilled async searches. Now
+ exporting the API commands. Bumped package version to 0.3.
+
+ * nns.tcl: Factored the argument processing for searches into a
+ * pkgIndex.tcl: separate command. Pseudo-public. Undocumented, but
+ * nns_client.man: can be used by other nameserver packages. Fixed
+ leak when encountering a missing name server during creation of
+ a continuous or async search. Fixed async destruction of a
+ continous search from receiver object. Now exporting the API
+ commands. Bumped package version to 0.4.
+
+2008-05-03 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * nns_auto.tcl (::nameserv::auto::configure): Fixed incorrect
+ * server.tcl (::nameserv::server::configure): checking for
+ * nns.tcl (::nameserv::configure): wrong#args in the code
+ * pkgIndex.tcl: handling the various options. Bumped client
+ * nns_client.man: to version 0.3.2, server to 0.3.1, and auto
+ * nns_server.man: to 0.2.1. Also general documentation work.
+ * nns_auto.man:
+ * nns_intro.man:
+
+2008-04-30 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * ../../apps/nns (::nns::ProcessCommandLine): Fixed incorrect
+ * ../../apps/nns.man: checking for wrong#args in the code handling
+ * ../../apps/nnsd.man: the options -host and -port. Reworked
+ the descriptiond of the applications a bit.
+
+ * ../../apps/nnslog: New application and its documentation, a
+ * ../../apps/nnslog.man: stripped down form of 'nns search
+ -continuous *' with different output (log of events).
+
+2008-04-30 Andreas Kupries <andreask@activestate.com>
+
+ * nns.tcl (::nameserv::configure): Fixed [Bug 1954771], bringing
+ * pkgIndex.tcl: missing variable into scope. Thanks to Joe Brandt
+ * nns_client:man: <vonbane@users.sourceforge.net> for both report
+ and fix. Bumped version to 0.3.1.
+
+2008-04-30 Andreas Kupries <andreask@activestate.com>
+
+ * nns_intro.man: New file, giving an overview of the packages and
+ * nns_auto.man: applications in the module. All other documents now
+ * nns_client.man: refer back to the introduction. Also clarified
+ * nns_common.man: the relationship to DNS, which is none, plus
+ * nns_protocol.man: reference to the Tcllib packages which do
+ * nns_server.man: handle DNS. Pointed applications out as examples
+ * ../../apps/nnsd.man: of use for the packages.
+ * ../../apps/nns.man:
+
+2008-04-03 Andreas Kupries <andreask@activestate.com>
+
+ * nns_protocol.man: Renamed nns_procotol.man, fixed the typo in
+ the filename. Thanks to Reinhard Max for seeing this.
+
+2008-03-14 Andreas Kupries <andreask@activestate.com>
+
+ * nns_client.man: Cleaned up a bit, replaced deprecated [lst_item]
+ usage with [def].
+
+2008-02-29 Andreas Kupries <andreask@activestate.com>
+
+ * nns_auto.tcl (::nameserv::auto::Rebind, ::nameserv::auto::bind):
+ * nns_auto.man: Fixed string match with bad pattern and missing
+ * pkgIndex.tcl: string to match against. Version bumped to 0.2.
+
+2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.10 ========================
+ *
+
+2007-07-18 Andreas Kupries <andreask@activestate.com>
+
+ * ../../apps/nnsd: Fixed option bug. Bumped version to 1.0.1.
+
+ * ../../apps/nns: Extended example client application with
+ * ../../apps/nns.man: continuous search. Further extended to
+ detect and handle loss of connection with service, by exiting.
+ Bumped version to 1.1
+
+ * server.tcl: Implemented asynchronous and continuous searches.
+ * nns.tcl: Ditto in client. Documented this feature, and the
+ * nns_client.man: extensions to the protocol it needs.
+ * nns_server.man: Bumped both server and client to version 0.3.
+ * nns_protocol.man:
+ * pkgIndex.tcl:
+
+2007-07-17 Andreas Kupries <andreask@activestate.com>
+
+ * nns_auto.tcl: Name service client on top of the basic facility.
+ * nns_auto.man: Provides automatic restoration of registered names
+ * pkgIndex.tcl: after a loss of connection.
+
+ * nns.tcl: Extended to use the services of 'uevent' to generate
+ events for important changes, like the loss of the connection to
+ the server. The package version is bumped to 0.2.
+
+2007-05-08 Andreas Kupries <andreask@activestate.com>
+
+ * nns_client.man: New name for nns.man, to avoid clashing with the
+ * nns.tcl: nns.man of the command line client, and removed unwanted
+ log output from the client package.
+
+ * ../../apps/nnsl: Merged nnsl and nnst into one command line client
+ * ../../apps/nnst: application, nns. Added documentation for that
+ * ../../apps/nns: application.
+ * ../../apps/nns.man
+
+ * ../../apps/nnsd.man: Added documentation for the command line
+ server application.
+
+ * nns_server.man: Changed configuration -local to -localonly
+ * server.tcl: for better understanding. Bumped to version 0.2
+ * pkgIndex.tcl: Removed unwanted log output.
+
+2007-05-07 Andreas Kupries <andreask@activestate.com>
+
+ * nns.man: Added documentation for client and server packages.
+ * nns_server.man:
+
+2007-05-04 Andreas Kupries <andreask@activestate.com>
+
+ * NNS - Nano Name Service.
+ Initial commit. TODO: Documentation for client and server, ditto
+ testsuites, are needed. Only the trivial code shared by both is
+ documented and tested. Manual testing has been done however,
+ using the nns* applications, see apps/
diff --git a/tcllib/modules/nns/common.tcl b/tcllib/modules/nns/common.tcl
new file mode 100644
index 0000000..5c3e1ca
--- /dev/null
+++ b/tcllib/modules/nns/common.tcl
@@ -0,0 +1,38 @@
+# -*- tcl -*-
+# ### ### ### ######### ######### #########
+## Name service - Common/shared information.
+
+# ### ### ### ######### ######### #########
+## Requirements
+
+namespace eval ::nameserv::common {}
+
+# ### ### ### ######### ######### #########
+## API
+
+proc ::nameserv::common::port {} {
+ variable port
+ return $port
+}
+
+namespace eval ::nameserv::common {
+ # Derivation of the standard port number for this service.
+
+ # nameserv::server
+ # -> nameservserver / remove ':'
+ # -> 62637378737837 / phonecode
+ # -> 38573 / mod 65536
+
+ variable port 38573
+
+ # The modulo operation is required because IP port numbers are
+ # restricted to unsigned short (16 bit), i.e. 1 ... 65535.
+}
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide nameserv::common 0.1
+
+##
+# ### ### ### ######### ######### #########
diff --git a/tcllib/modules/nns/common.test b/tcllib/modules/nns/common.test
new file mode 100644
index 0000000..c23873c
--- /dev/null
+++ b/tcllib/modules/nns/common.test
@@ -0,0 +1,34 @@
+# -*- tcl -*-
+# common.test: Tests for the common code of the name service
+#
+# Sourcing this file into Tcl runs the tests and generates output for
+# errors. No output means no errors were found.
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8
+testsNeedTcltest 1.0
+
+testing {
+ useLocal common.tcl nameserv::common
+}
+
+# -------------------------------------------------------------------------
+
+test names-common-1.0 {get IP port number, wrong#args, too many} {
+ catch {nameserv::common::port a} msg
+ set msg
+} [tcltest::tooManyArgs nameserv::common::port {}]
+
+test names-common-2.0 {get IP port number} {
+ nameserv::common::port
+} 38573
+
+# -------------------------------------------------------------------------
+
+testsuiteCleanup
+return
diff --git a/tcllib/modules/nns/nns.tcl b/tcllib/modules/nns/nns.tcl
new file mode 100644
index 0000000..e256206
--- /dev/null
+++ b/tcllib/modules/nns/nns.tcl
@@ -0,0 +1,432 @@
+# -*- tcl -*-
+# ### ### ### ######### ######### #########
+## Name Service - Client side access
+
+# ### ### ### ######### ######### #########
+## Requirements
+
+package require Tcl 8.4
+package require comm ; # Generic message transport
+package require interp ; # Interpreter helpers.
+package require logger ; # Tracing internal activity
+package require nameserv::common ; # Common/shared utilities
+package require snit ; # OO support, for streaming search class
+package require uevent ; # Generate events for connection-loss
+
+namespace eval ::nameserv {}
+
+# ### ### ### ######### ######### #########
+## API: Write, Read, Search
+
+proc ::nameserv::bind {name data} {
+ # Registers this application at the configured name service under
+ # the specified name, and provides a value.
+ #
+ # Note: The application is allowed register multiple names.
+ #
+ # Note: A registered name is automatically removed by the server
+ # when the connection to it collapses.
+
+ DO Bind $name $data
+ return
+}
+
+proc ::nameserv::release {} {
+ # Releases all names the application has registered at the
+ # configured name service.
+
+ DO Release
+ return
+}
+
+proc ::nameserv::search {args} {
+ # Searches the configured name service for applications whose name
+ # matches the given pattern. Returns a dictionary mapping from the
+ # names to the data they provided at 'bind' time.
+
+ # In continuous and async modes it returns an object whose
+ # contents reflect the current set of matching entries.
+
+ array set a [search-parseargs $args]
+ upvar 0 a(oneshot) oneshot
+ upvar 0 a(continuous) continuous
+ upvar 0 a(pattern) pattern
+
+ if {$continuous} {
+ variable search
+ # This client uses the receiver object as tag for the search
+ # in the service. This is easily unique, and makes dispatch of
+ # incoming results later easy too.
+
+ set receiver [receiver %AUTO% $oneshot]
+ if {[catch {
+ ASYNC Search/Continuous/Start $receiver $pattern
+ } err]} {
+ # Release the allocated object to prevent a leak, then
+ # rethrow the error.
+ $receiver destroy
+ return -code error $err
+ }
+
+ set search($receiver) .
+ return $receiver
+ } else {
+ return [DO Search $pattern]
+ }
+}
+
+proc ::nameserv::protocol {} {
+ return 1
+}
+
+proc ::nameserv::server_protocol {} {
+ return [DO ProtocolVersion]
+}
+
+proc ::nameserv::server_features {} {
+ return [DO ProtocolFeatures]
+}
+
+# ### ### ### ######### ######### #########
+## semi-INT: search argument processing.
+
+proc ::nameserv::search-parseargs {arguments} {
+ # This command is semi-public. It is not documented for public
+ # use, however the package nameserv::auto uses as helper in its
+ # implementation of the search command.
+
+ switch -exact [llength $arguments] {
+ 0 {
+ set oneshot 0
+ set continuous 0
+ set pattern *
+ }
+ 1 {
+ set opt [lindex $arguments 0]
+ if {$opt eq "-continuous"} {
+ set oneshot 0
+ set continuous 1
+ set pattern *
+ } elseif {$opt eq "-async"} {
+ set oneshot 1
+ set continuous 1
+ set pattern *
+ } else {
+ set oneshot 0
+ set continuous 0
+ set pattern $opt
+ }
+ }
+ 2 {
+ set opt [lindex $arguments 0]
+ if {$opt eq "-continuous"} {
+ set oneshot 0
+ set continuous 1
+ set pattern [lindex $arguments 1]
+ } elseif {$opt eq "-async"} {
+ set oneshot 1
+ set continuous 1
+ set pattern [lindex $arguments 1]
+ } else {
+ return -code error "wrong\#args: Expected ?-continuous|-async? ?pattern?"
+ }
+ }
+ default {
+ return -code error "wrong\#args: Expected ?-continuous|-async? ?pattern?"
+ }
+ }
+
+ return [list oneshot $oneshot continuous $continuous pattern $pattern]
+}
+
+# ### ### ### ######### ######### #########
+## INT: Communication setup / teardown / use
+
+proc ::nameserv::DO {args} {
+ variable sid
+ log::debug [linsert $args end @ $sid]
+
+ if {[catch {
+ [SERV] send $sid $args
+ #eval [linsert $args 0 [SERV] send $sid] ;# $args
+ } msg]} {
+ if {[string match "*refused*" $msg]} {
+ return -code error "No name server present @ $sid"
+ } else {
+ return -code error $msg
+ }
+ }
+ # Result of the call
+ return $msg
+}
+
+proc ::nameserv::ASYNC {args} {
+ variable sid
+ log::debug [linsert $args end @ $sid]
+
+ if {[catch {
+ [SERV] send -async $sid $args
+ #eval [linsert $args 0 [SERV] send $sid] ;# $args
+ } msg]} {
+ if {[string match "*refused*" $msg]} {
+ return -code error "No name server present @ $sid"
+ } else {
+ return -code error $msg
+ }
+ }
+ # No result to return
+ return
+}
+
+proc ::nameserv::SERV {} {
+ variable comm
+ variable sid
+ variable host
+ variable port
+ if {$comm ne ""} {return $comm}
+
+ # NOTE
+ # -local 1 means that clients can only talk to a local
+ # name service. Might make sense to auto-force
+ # -local 0 for host ne "localhost".
+
+ set interp [interp::createEmpty]
+ foreach msg {
+ Search/Continuous/Change
+ } {
+ interp alias $interp $msg {} ::nameserv::$msg
+ }
+
+ set sid [list $port $host]
+ set comm [comm::comm new ::nameserv::CSERV \
+ -interp $interp \
+ -local 1 \
+ -listen 1]
+
+ $comm hook lost ::nameserv::LOST
+
+ log::debug [list SERV @ $sid : $comm]
+ return $comm
+}
+
+proc ::nameserv::LOST {args} {
+ upvar 1 id id chan chan reason reason
+ variable comm
+ variable sid
+ variable search
+
+ log::debug [list LOST @ $sid - $reason]
+
+ $comm destroy
+
+ set comm {}
+ set sid {}
+
+ # Notify async/cont search of the loss.
+ foreach r [array names search] {
+ $r DATA stop
+ unset search($r)
+ }
+
+ uevent::generate nameserv lost-connection [list reason $reason]
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Initialization - System state
+
+namespace eval ::nameserv {
+ # Object command of the communication channel to the server.
+ # If present re-configuration is not possible. Also the comm
+ # id of the server.
+
+ variable comm {}
+ variable sid {}
+
+ # Table of active async/cont searches
+
+ variable search ; array set search {}
+}
+
+# ### ### ### ######### ######### #########
+## API: Configuration management (host, port)
+
+proc ::nameserv::cget {option} {
+ return [configure $option]
+}
+
+proc ::nameserv::configure {args} {
+ variable host
+ variable port
+ variable comm
+
+ if {![llength $args]} {
+ return [list -host $host -port $port]
+ }
+ if {[llength $args] == 1} {
+ # cget
+ set opt [lindex $args 0]
+ switch -exact -- $opt {
+ -host { return $host }
+ -port { return $port }
+ default {
+ return -code error "bad option \"$opt\", expected -host, or -port"
+ }
+ }
+ }
+
+ if {$comm ne ""} {
+ return -code error "Unable to configure an active connection"
+ }
+
+ # Note: Should -port/-host be made configurable after
+ # communication has started it will be necessary to provide code
+ # which retracts everything from the old server and re-initializes
+ # the new one.
+
+ while {[llength $args]} {
+ set opt [lindex $args 0]
+ switch -exact -- $opt {
+ -host {
+ if {[llength $args] < 2} {
+ return -code error "value for \"$opt\" is missing"
+ }
+ set host [lindex $args 1]
+ set args [lrange $args 2 end]
+ }
+ -port {
+ if {[llength $args] < 2} {
+ return -code error "value for \"$opt\" is missing"
+ }
+ set port [lindex $args 1]
+ # Todo: Check non-zero unsigned short integer
+ set args [lrange $args 2 end]
+ }
+ default {
+ return -code error "bad option \"$opt\", expected -host, or -port"
+ }
+ }
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Receiver for continuous and async searches
+
+proc ::nameserv::Search/Continuous/Change {tag type response} {
+
+ # Ignore messages for searches which were canceled already.
+ #
+ # Due to the async nature of the messages for cont/async search
+ # the client may have canceled the receiver object already, sent
+ # the stop message already, but still has to process search
+ # results which were already in flight. We ignore them.
+
+ if {![llength [info commands $tag]]} return
+
+ # This client uses the receiver object as tag, dispatch the
+ # received notification to it.
+
+ $tag DATA $type $response
+ return
+}
+
+snit::type ::nameserv::receiver {
+ option -command -default {}
+
+ constructor {{once 0}} {
+ set singleshot $once
+ return
+ }
+
+ destructor {
+ if {$singleshot} return
+ ::nameserv::ASYNC Search/Continuous/Stop $self
+ Callback stop {}
+ return
+ }
+
+ method get {k} {
+ if {![info exists current($k)]} {return -code error "Unknown key \"$k\""}
+ return $current($k)
+ }
+
+ method names {} {
+ return [array names current]
+ }
+
+ method size {} {
+ return [array size current]
+ }
+
+ method getall {{pattern *}} {
+ return [array get current $pattern]
+ }
+
+ method filled {} {
+ return $filled
+ }
+
+ method {DATA stop} {} {
+ if {$filled && $singleshot} return
+ set singleshot 1 ; # Prevent 'stop' again during destruction.
+ Callback stop {}
+ return
+ }
+
+ method {DATA add} {response} {
+ set filled 1
+ if {$singleshot} {
+ ASYNC Search/Continuous/Stop $self
+ }
+ array set current $response
+ Callback add $response
+ if {$singleshot} {
+ Callback stop {}
+ }
+ return
+ }
+
+ method {DATA remove} {response} {
+ set filled 1
+ foreach {k v} $response {
+ unset -nocomplain current($k)
+ }
+ Callback remove $response
+ return
+ }
+
+ proc Callback {type response} {
+ upvar 1 options options
+ if {$options(-command) eq ""} return
+ # Defer execution to event loop
+ after 0 [linsert $options(-command) end $type $response]
+ return
+ }
+
+ variable singleshot 0
+ variable current -array {}
+ variable filled 0
+}
+
+# ### ### ### ######### ######### #########
+## Initialization - Tracing, Configuration
+
+logger::initNamespace ::nameserv
+namespace eval ::nameserv {
+ # Host and port to connect to, to get access to the nameservice.
+
+ variable host localhost
+ variable port [nameserv::common::port]
+
+ namespace export bind release search protocol \
+ server_protocol server_features configure cget
+}
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide nameserv 0.4.2
+
+##
+# ### ### ### ######### ######### #########
diff --git a/tcllib/modules/nns/nns_auto.man b/tcllib/modules/nns/nns_auto.man
new file mode 100644
index 0000000..2547073
--- /dev/null
+++ b/tcllib/modules/nns/nns_auto.man
@@ -0,0 +1,119 @@
+[manpage_begin nameserv::auto n 0.3]
+[see_also nameserv(n)]
+[keywords automatic]
+[keywords client]
+[keywords {name service}]
+[keywords reconnect]
+[keywords restore]
+[copyright {2007-2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, Client Extension}]
+[category Networking]
+[require Tcl 8.4]
+[require nameserv::auto [opt 0.3]]
+[require nameserv]
+[description]
+
+Please read the document [term {Name service facility, introduction}]
+first.
+
+[para]
+
+This package provides the [emph exact] same API as is provided by
+package [package nameserv], i.e. the regular name service client. It
+differs from the former by taking measures to ensure that longer-lived
+data, i.e. bound names, continuous and unfullfilled async searches,
+survive the loss of the connection to the name server as much as is
+possible.
+
+[para]
+
+This means that the bound names and continuous and unfullfilled async
+searches are remembered client-side and automatically re-entered into
+the server when the connection comes back after its loss. For bound
+names there is one important limitation to such restoration: It is
+possible that a name of this client was bound by a different client
+while the connection was gone. Such names are fully lost, and the best
+the package can and will do is to inform the user of this.
+
+[section API]
+
+The user-visible API is mainly identical to the API of [package nameserv]
+and is therefore not described here. Please read the documentation of
+[package nameserv].
+
+[para]
+
+The differences are explained below, in the sections [sectref OPTIONS] and
+[sectref EVENTS].
+
+[section OPTIONS]
+
+This package supports all the options of package [package nameserv],
+plus one more. The additional option allows the user to specify the
+time interval between attempts to restore a lost connection.
+
+[list_begin options]
+[opt_def -delay [arg milliseconds]]
+
+The value of this option is an integer value > 0 which specifies the
+interval to wait between attempts to restore a lost connection, in
+milliseconds. The default value is [const 1000], i.e. one second.
+
+[list_end]
+
+[section EVENTS]
+
+This package generates all of the events of package [package nameserv],
+plus two more. Both events are generated for the tag [term nameserv].
+
+[list_begin definitions]
+[def [term lost-name]]
+
+This event is generated when a bound name is truly lost, i.e. could
+not be restored after the temporary loss of the connection to the name
+server. It indicates that a different client took ownership of the
+name while this client was out of contact.
+
+[para]
+
+The detail information of the event will be a Tcl dictionary
+containing two keys, [const name], and [const data]. Their values hold
+all the information about the lost name.
+
+[def [term re-connection]]
+
+This event is generated when the connection to the server is
+restored. The remembered data has been restored when the event is
+posted.
+
+[para]
+
+The event has no detail information.
+
+[list_end]
+
+[section DESIGN]
+
+The package is implemented on top of the regular nameservice client,
+i.e. package [package nameserv]. It detects the loss of the
+connection by listening for [term lost-connection] events, on the tag
+[term nameserv].
+
+[para]
+
+It reacts to such events by starting a periodic timer and trying to
+reconnect to the server whenver this timer triggers. On success the
+timer is canceled, a [term re-connection] event generated, and the
+package proceeds to re-enter the remembered bound names and continuous
+searches.
+
+[para]
+
+Another loss of the connection, be it during or after re-entering the
+remembered information simply restarts the timer and subsequent
+reconnection attempts.
+
+[vset CATEGORY nameserv]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/nns/nns_auto.tcl b/tcllib/modules/nns/nns_auto.tcl
new file mode 100644
index 0000000..b081741
--- /dev/null
+++ b/tcllib/modules/nns/nns_auto.tcl
@@ -0,0 +1,443 @@
+# -*- tcl -*-
+# ### ### ### ######### ######### #########
+## Name Service - Client side connection monitor
+
+# ### ### ### ######### ######### #########
+## Requirements
+
+package require nameserv 0.4.1 ; # Name service client-side core
+package require uevent ; # Watch for connection-loss
+
+namespace eval ::nameserv::auto {}
+
+# ### ### ### ######### ######### #########
+## API: Write, Read, Search
+
+proc ::nameserv::auto::bind {name data} {
+ # See nameserv::bind. Remembers the information, for re-binding
+ # when the connection was lost, and later restored.
+
+ # Note: Enter has a return value we do not want, bind has no
+ # return value. Otherwise 'Enter' would not be necessary and
+ # simply be 'bind'.
+
+ Enter $name $data normal
+ return
+}
+
+proc ::nameserv::auto::release {} {
+ # Releases all names the application has registered at the
+ # configured name service.
+ variable bindings
+ variable timer
+
+ array unset bindings *
+ if {$timer ne ""} {
+ # Actually release the data only if the connection is
+ # currently not lost. Otherwise they are gone already, and
+ # just forgetting them here (see above) was enough.
+ nameserv::release
+ }
+ return
+}
+
+proc ::nameserv::auto::search {args} {
+ variable searches
+
+ # Note: Here we are using a semi-public command of 'nameserv' to
+ # parse the search arguments on our own to determine if we need
+ # the persistence or not.
+
+ array set a [nameserv::search-parseargs $args]
+ upvar 0 a(oneshot) oneshot
+ upvar 0 a(continuous) continuous
+ upvar 0 a(pattern) pattern
+
+ if {!$continuous} {
+ # Result is direct result of the search, pass through to
+ # caller, nothing to persist.
+
+ return [eval [linsert $args 0 ::nameserv::search]]
+ # 8.5: return [nameserv::search {*}$args]
+ }
+
+ # Continuous or async search. The result we got is a receiver
+ # object. Wrap our own persistent receiver around it so that it
+ # can handle a loss of connection while we are waiting for the
+ # search result.
+
+ return [receiver %AUTO% $oneshot $args]
+}
+
+proc ::nameserv::auto::protocol {} {
+ return [nameserv::protocol]
+}
+
+proc ::nameserv::auto::server_protocol {} {
+ return [nameserv::server_protocol]
+}
+
+proc ::nameserv::auto::server_features {} {
+ return [nameserv::server_features]
+}
+
+# ### ### ### ######### ######### #########
+## Internal helper commands.
+
+proc ::nameserv::auto::Reconnect {args} {
+ # args = <>|<tags event details>
+ # <tag,event> = <'nameserv','lost'>
+ # details = dict ('reason' -> string)
+
+ StopReconnect
+
+ if {![catch {
+ ::nameserv::server_features
+ }]} {
+ # Note: Reloss of connection during Rebind will also
+ # StartReconnect
+ Rebind
+ return
+ }
+
+ StartReconnect
+ return
+}
+
+proc ::nameserv::auto::Rebind {} {
+ variable bindings
+ variable searches
+
+ foreach {name data} [array get bindings] {
+ if {![Enter $name $data restore]} return
+ }
+
+ foreach receiver [array names searches] {
+ if {![$receiver restore]} return
+ }
+
+ # Fully restored, time to notify interested parties
+ uevent::generate nameserv re-connection {}
+ return
+}
+
+proc ::nameserv::auto::Enter {name data how} {
+ variable bindings
+
+ # Remember locally for possible loss of connection ...
+ set bindings($name) $data
+
+ # ... then forward to name server
+ if {[catch {
+ nameserv::bind $name $data
+ } msg]} {
+ # Problem with server while (re)binding a name.
+
+ if {[string match {*No name server*} $msg]} {
+ # Lost the server (again), while (re)binding a name. Abort
+ # and restart the watcher waiting for the server to come
+ # back.
+ StartReconnect
+ return 0
+ }
+
+ # Other error => (name already bound). This means that someone
+ # else took the name while we were not connected to the
+ # service, or the name was bound before the call anyway. The
+ # reaction depends on our entry point. For regular bind we
+ # return the error as is to keep API compatibility. During
+ # restoration OTOH the best effort we can do is to deliver a
+ # note about the total loss of this binding to all interested
+ # observers via event. Additionally remove the lost item from
+ # the set of names to remember. Note that there is no need to
+ # restart the watcher, the server was _not_ lost.
+
+ unset bindings($name)
+ if {$how eq "normal"} {
+ return -code $msg
+ } else {
+ uevent::generate nameserv lost-name [list name $name data $data]
+ return 1
+ }
+ }
+
+ # Success, nothing further to do.
+ return 1
+}
+
+# ### ### ### ######### ######### #########
+## Management of the reconnect timer.
+
+proc ::nameserv::auto::StartReconnect {} {
+ variable timer
+ variable delay
+ if {$timer ne ""} return
+ set timer [after $delay ::nameserv::auto::Reconnect]
+ return
+}
+
+proc ::nameserv::auto::StopReconnect {} {
+ variable timer ""
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Persistent receiver for continuous and async searches.
+
+snit::type ::nameserv::auto::receiver {
+
+ option -command -default {}
+
+ constructor {once search} {
+ set mysingleshot $once
+ set mysearch $search
+ $self restore ; # Create internal volatile receiver.
+ return
+ }
+
+ destructor {
+ if {$myreceiver ne ""} { $myreceiver destroy }
+ if {$mysingleshot} return
+ Callback stop {}
+ return
+ }
+
+ method restore {} {
+ set nameserv::auto::searches($self) .
+
+ if {[catch {
+ set result [eval [linsert $mysearch 0 ::nameserv::search]]
+ # 8.5: set result [nameserv::search {*}$mysearch]
+ } msg]} {
+ # Problem with server while restoring a search.
+
+ if {[string match {*No name server*} $msg]} {
+ # Lost the server (again), while restoring the search.
+ # Abort and restart the watcher waiting for the server
+ # to come back.
+ ::nameserv::auto::StartReconnect
+ return 0
+ }
+
+ # Rethrow other problems.
+ return -code error $msg
+ }
+
+ # Restored, prepare ourselves
+ set myreceiver $result
+ set myclear 1 ; # Have to clear previous data when
+ # the new set comes in.
+ $myreceiver configure -command [mymethod DATA]
+ return 1
+ }
+
+ method get {k} {
+ if {![info exists mycurrent($k)]} {return -code error "Unknown key \"$k\""}
+ return $current($k)
+ }
+
+ method names {} {
+ return [array names mycurrent]
+ }
+
+ method size {} {
+ return [array size mycurrent]
+ }
+
+ method getall {{pattern *}} {
+ return [array get mycurrent $pattern]
+ }
+
+ method filled {} {
+ return $myfilled
+ }
+
+ # Handler for events coming from the breakable search.
+
+ method {DATA stop} {args} {
+ # Ignore the response dict, it is empty anyway.
+ # Get rid of the volatile receiver.
+ if {$myreceiver ne ""} { $myreceiver destroy }
+ # Oneshot handling happened already.
+ return
+ }
+
+ method {DATA add} {response} {
+ # New entries to handle
+ set myfilled 1
+ if {$mysingleshot} {
+ # The search was async and is now done, therefore we can
+ # get rid of the volatile receiver and do not have to care
+ # about the loss of the connection any longer.
+ $myreceiver destroy
+ set myreceiver ""
+ unset ::nameserv::auto::searches($self)
+ }
+ if {$myclear} {
+ # Handle a refill after a connection loss, the new data
+ # overwrites everything known before.
+ array unset mycurrent *
+ set myclear 0
+ }
+ array set mycurrent $response
+ Callback add $response
+ if {$mysingleshot} {
+ Callback stop {}
+ }
+ return
+ }
+
+ method {DATA remove} {response} {
+ set myfilled 1
+ foreach {k v} $response {
+ unset -nocomplain mycurrent($k)
+ }
+ Callback remove $response
+ return
+ }
+
+ # Run our own callback.
+
+ proc Callback {type response} {
+ upvar 1 options options
+ if {$options(-command) eq ""} return
+ # Defer execution to event loop
+ after 0 [linsert $options(-command) end $type $response]
+ return
+ }
+
+ # Search state
+
+ variable mysingleshot 0 ; # Bool flag, set if search is
+ # async, not continous.
+ variable mycurrent -array {} ; # Current state of search results
+ variable myfilled 0 ; # Bool flag, set when result has arrived.
+
+ variable mysearch "" ; # Copy of search definition, for
+ # its restoration after our
+ # connection to the service was
+ # restored.
+ variable myclear 0 ; # Bool flag, set when state has to
+ # be cleared before adding new
+ # data, for refill after a
+ # connection has been restored.
+ variable myreceiver "" ; # Volatile breakable regular search
+ # receiver.
+}
+
+# ### ### ### ######### ######### #########
+## Initialization - System state
+
+namespace eval ::nameserv::auto {
+ # In-memory database of bindings to restore after connection was
+ # lost and restored.
+
+ variable bindings ; array set bindings {}
+
+ # In-memory database of continuous and unfulfilled async searches
+ # to restore after the connection was lost and restored.
+
+ variable searches ; array set searches {}
+
+ # Handle of the timer used to periodically try to reconnect with
+ # the server in the case it was lost.
+
+ variable timer ""
+}
+
+# ### ### ### ######### ######### #########
+## API: Configuration management (host, port)
+
+proc ::nameserv::auto::cget {option} {
+ return [configure $option]
+}
+
+proc ::nameserv::auto::configure {args} {
+ variable delay
+
+ if {![llength $args]} {
+ # Merge the underlying configuration with the local settings
+ # before returning.
+ return [linsert [nameserv::configure] 0 -delay $delay]
+ }
+ if {[llength $args] == 1} {
+ # cget
+ set opt [lindex $args 0]
+ switch -exact -- $opt {
+ -delay { return $delay }
+ default {
+ # Not a local option, check with underlying package
+ # before throwing an error.
+ if {![catch {
+ nameserv::cget $opt
+ } v]} {
+ return $v
+ }
+ return -code error "[string map {{expected } {expected -delay, }} $v]"
+ }
+ }
+ }
+
+ while {[llength $args]} {
+ set opt [lindex $args 0]
+ switch -exact -- $opt {
+ -delay {
+ if {[llength $args] < 2} {
+ return -code error "value for \"$opt\" is missing"
+ }
+ set delay [lindex $args 1]
+ set args [lrange $args 2 end]
+
+ # Using the 'incr' hack instead of 'string is integer'
+ # allows delays larger than 32bit in Tcl 8.5.
+ if {[catch {incr delay 0}]} {
+ return -code error "bad value for \"$opt\", expected integer, got \"$delay\""
+ } elseif {$delay <= 0} {
+ return -code error "bad value for \"$opt\", is not greater than zero"
+ }
+ }
+ default {
+ # Not a local option, check with underlying package
+ # before throwing an error.
+ if {[catch {
+ nameserv::configure $opt [lindex $args 1]
+ } v]} {
+ if {[string match {bad option*} $v]} {
+ # Fix list of options in error before rethrowing.
+ return -code error "[string map {{expected } {expected -delay, }} $v]"
+ } else {
+ # Rethrow error unchanged
+ return -code error $v
+ }
+ }
+ # No error, option is processed, continue after it.
+ set args [lrange $args 2 end]
+ }
+ }
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Initialization - Tracing, Configuration
+
+logger::initNamespace ::nameserv::auto
+namespace eval ::nameserv::auto {
+ # Interval between reconnection attempts when connection was lost.
+
+ variable delay 1000 ; # One second
+
+ namespace export bind release search protocol \
+ server_protocol server_features configure cget
+}
+
+# Watch the base client for the loss of the connection.
+uevent::bind nameserv lost-connection ::nameserv::auto::Reconnect
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide nameserv::auto 0.3
+
+##
+# ### ### ### ######### ######### #########
diff --git a/tcllib/modules/nns/nns_client.man b/tcllib/modules/nns/nns_client.man
new file mode 100644
index 0000000..60068c5
--- /dev/null
+++ b/tcllib/modules/nns/nns_client.man
@@ -0,0 +1,338 @@
+[manpage_begin nameserv n 0.4.2]
+[see_also nameserv::common(n)]
+[see_also nameserv::server(n)]
+[keywords client]
+[keywords {name service}]
+[copyright {2007-2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, Client}]
+[category Networking]
+[require Tcl 8.4]
+[require nameserv [opt 0.4.2]]
+[require comm]
+[require logger]
+[description]
+
+Please read [term {Name service facility, introduction}] first.
+
+[para]
+
+This package provides a client for the name service facility
+implemented by the package [package nameserv::server].
+
+[para]
+
+This service is built in top of and for the package [package comm].
+It has nothing to do with the Internet's Domain Name System. If the
+reader is looking for a package dealing with that please see Tcllib's
+packages [package dns] and [package resolv].
+
+[section API]
+
+The package exports eight commands, as specified below:
+
+[list_begin definitions]
+
+[call [cmd ::nameserv::bind] [arg name] [arg data]]
+
+The caller of this command registers the given [arg name] as its name
+in the configured name service, and additionally associates a piece of
+[arg data] with it. The service does nothing with this information
+beyond storing it and delivering it as part of search results. The
+meaning is entirely up to the applications using the name service.
+
+[para]
+
+A generally useful choice would for example be an identifier for a
+communication endpoint managed by the package [package comm]. Anybody
+retrieving the name becomes immediately able to talk to this endpoint,
+i.e. the registering application.
+
+[para]
+
+Of further importance is that a caller can register itself under more
+than one name, and each name can have its own piece of [arg data].
+
+[para]
+
+Note that the name service, and thwerefore this command, will throw an
+error if the chosen name is already registered.
+
+[call [cmd ::nameserv::release]]
+
+Invoking this command releases all names (and their data) registered
+by all previous calls to [cmd ::nameserv::bind] of this client. Note
+that the name service will run this command implicitly when it loses
+the connection to this client.
+
+[call [cmd ::nameserv::search] [opt [option -async]|[option -continuous]] [opt [arg pattern]]]
+
+This command searches the name service for all registered names
+matching the specified glob-[arg pattern]. If not specified the
+pattern defaults to [const *], matching everything. The result of the
+command is a dictionary mapping the matching names to the data
+associated with them at [term bind]-time.
+
+[para]
+
+If either option [option -async] or [option -continuous] were
+specified the result of this command changes and becomes the Tcl
+command of an object holding the actual result.
+
+These two options are supported if and only if the service the client
+is connected to supports the protocol feature
+[term Search/Continuous].
+
+[para]
+
+For [option -async] the result object is asynchronously filled with
+the entries matching the pattern at the time of the search and then
+not modified any more.
+
+The option [option -continuous] extends this behaviour by additionally
+continuously monitoring the service for the addition and removal of
+entries which match the pattern, and updating the object's contents
+appropriately.
+
+[para]
+
+[emph Note] that the caller is responsible for configuring the object
+with a callback for proper notification when the current result (or
+further changes) arrive.
+
+[para]
+
+For more information about this object see section
+[sectref {ASYNCHRONOUS AND CONTINUOUS SEARCHES}].
+
+[call [cmd ::nameserv::protocol]]
+
+This command returns the highest version of the name service protocol
+supported by the package.
+
+[call [cmd ::nameserv::server_protocol]]
+
+This command returns the highest version of the name service protocol
+supported by the name service the client is currently connected to.
+
+[call [cmd ::nameserv::server_features]]
+
+This command returns a list containing the names of the features of
+the name service protocol which are supported by the name service the
+client is currently connected to.
+
+[call [cmd ::nameserv::cget] [option -option]]
+
+This command returns the currently configured value for the specified
+[option -option]. The list of supported options and their meaning can
+be found in section [sectref OPTIONS].
+
+[call [cmd ::nameserv::configure]]
+
+In this form the command returns a dictionary of all supported
+options, and their current values. The list of supported options and
+their meaning can be found in section [sectref OPTIONS].
+
+[call [cmd ::nameserv::configure] [option -option]]
+
+In this form the command is an alias for
+"[cmd ::nameserv::cget] [option -option]]".
+
+The list of supported options and their meaning can be found in
+section [sectref OPTIONS].
+
+[call [cmd ::nameserv::configure] "[option -option] [arg value]..."]
+
+In this form the command is used to configure one or more of the
+supported options. At least one option has to be specified, and each
+option is followed by its new value.
+
+The list of supported options and their meaning can be found in
+section [sectref OPTIONS].
+
+[para]
+
+This form can be used only as long as the client has not contacted the
+name service yet. After contact has been made reconfiguration is not
+possible anymore. This means that this form of the command is for the
+initalization of the client before it use.
+
+The command forcing a contact with the name service are
+
+[list_begin commands]
+[cmd_def bind]
+[cmd_def release]
+[cmd_def search]
+[cmd_def server_protocol]
+[cmd_def server_features]
+[list_end]
+[list_end]
+
+[section {CONNECTION HANDLING}]
+
+The client automatically connects to the service when one of the
+commands below is run for the first time, or whenever one of the
+commands is run after the connection was lost, when it was lost.
+
+[para]
+[list_begin commands]
+[cmd_def bind]
+[cmd_def release]
+[cmd_def search]
+[cmd_def server_protocol]
+[cmd_def server_features]
+[list_end]
+[para]
+
+Since version 0.2 of the client it will generate an event when the
+connection is lost, allowing higher layers to perform additional
+actions. This is done via the support package [package uevent]. This
+and all other name service related packages hereby reserve the
+uevent-tag [term nameserv]. All their events will be posted to that
+tag.
+
+[section EVENTS]
+
+This package generates only one event, [term lost-connection]. The
+detail information provided to that event is a Tcl dictionary. The
+only key contained in the dictionnary is [const reason], and its value
+will be a string describing why the connection was lost.
+
+This string is supplied by the underlying communication package,
+i.e. [package comm].
+
+[section OPTIONS]
+
+The options supported by the client are for the specification of which
+name service to contact, i.e. of the location of the name service.
+
+They are:
+
+[list_begin options]
+[opt_def -host [arg name]|[arg ipaddress]]
+
+This option specifies the host name service to contact is running on,
+either by [arg name], or by [arg ipaddress]. The initial default is
+[const localhost], i.e. it is expected to contact a name service
+running on the same host as the application using this package.
+
+[opt_def -port [arg number]]
+
+This option specifies the port the name service to contact is
+listening on. It has to be a positive integer number (> 0) not greater
+than 65536 (unsigned short). The initial default is the number
+returned by the command [cmd ::nameserv::common::port], as provided by
+the package [package ::nameserv::common].
+
+[list_end]
+
+[section {ASYNCHRONOUS AND CONTINUOUS SEARCHES}]
+
+Asynchronous and continuous searches are invoked by using either
+option [option -async] or [option -continuous] as argument to the
+command [cmd ::nameserv::search].
+
+[para]
+
+[emph Note] that these two options are supported if and only if the
+service the client is connected to supports the protocol feature
+[term Search/Continuous]. The service provided by the package
+[package ::nameserv::server] does this since version 0.3.
+
+[para]
+
+For such searches the result of the search command is the Tcl command
+of an object holding the actual result. The API provided by these
+objects is:
+
+[list_begin definitions]
+
+[def Options:]
+[list_begin options]
+[opt_def -command [arg command_prefix]]
+
+This option has to be set if a user of the result object wishes to get
+asynchronous notifications when the search result or changes to it
+arrive.
+
+[para]
+
+[emph Note] that while it is possible to poll for the arrival of the
+initial search result via the method [method filled], and for
+subsequent changes by comparing the output of method [method getall]
+against a saved copy, this is not the recommended behaviour. Setting
+the [option -command] callback and processing the notifications as
+they arrive is much more efficient.
+
+[para]
+
+The [arg command_prefix] is called with two arguments, the type of
+change, and the data of the change. The type is either [const add] or
+[const remove], indicating new data, or deleted data, respectively.
+The data of the change is always a dictionary listing the
+added/removed names and their associated data.
+
+[para]
+
+The first change reported for a search is always the set of matching
+entries at the time of the search.
+
+[list_end]
+
+[def Methods:]
+[list_begin definitions]
+
+[call [cmd \$result] [method destroy]]
+
+Destroys the object and cancels any continuous monitoring of the
+service the object may have had active.
+
+[call [cmd \$result] [method filled]]
+
+The result is a boolean value indicating whether the search result has
+already arrived ([const True]), or not ([const False]).
+
+[call [cmd \$result] [method get] [arg name]]
+
+Returns the data associated with the given [arg name] at
+[term bind]-time.
+
+[call [cmd \$result] [method names]]
+
+Returns a list containing all names known to the object at the time of
+the invokation.
+
+[call [cmd \$result] [method size]]
+
+Returns an integer value specifying the size of the result at the time
+of the invokation.
+
+[call [cmd \$result] [method getall] [opt [arg pattern]]]
+
+Returns a dictionary containing the search result at the time of the
+invokation, mapping the matching names to the data associated with
+them at [term bind]-time.
+
+[list_end]
+[list_end]
+
+[section HISTORY]
+[list_begin definitions]
+[def 0.3.1]
+Fixed SF Bug 1954771.
+
+[def 0.3]
+Extended the client with the ability to perform asynchronous and
+continuous searches.
+
+[def 0.2]
+Extended the client with the ability to generate events when it loses
+its connection to the name service. Based on package [package uevent].
+
+[def 0.1]
+Initial implementation of the client.
+[list_end]
+
+[vset CATEGORY nameserv]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/nns/nns_cluster.tcl b/tcllib/modules/nns/nns_cluster.tcl
new file mode 100644
index 0000000..6d49c1a
--- /dev/null
+++ b/tcllib/modules/nns/nns_cluster.tcl
@@ -0,0 +1,499 @@
+# -*- tcl -*-
+# ### ### ### ######### ######### #########
+## Name Service - Cluster
+
+# ### ### ### ######### ######### #########
+## Requirements
+
+package require Tcl 8.5
+package require comm ; # Generic message transport
+package require interp ; # Interpreter helpers.
+package require logger ; # Tracing internal activity
+package require uuid
+package require cron
+package require nettool 0.4
+package require udp
+
+namespace eval ::comm {}
+::namespace eval ::cluster {}
+
+###
+# This package implements an ad/hoc zero configuration
+# like network of comm (and other) network connections
+###
+
+###
+# topic: 5cffdc91e554c923ebe43df13fac77d5
+###
+proc ::cluster::broadcast {args} {
+ if {$::cluster::config(debug)} {
+ puts [list $::cluster::local_pid SEND $args]
+ }
+ while {[catch {
+ set sock [listen]
+ puts -nonewline $sock [list [pid] {*}$args]
+ flush $sock
+ } error]} {
+ set ::cluster::broadcast_sock {}
+ if {$::cluster::config(debug)} {
+ puts "Broadcast ERR: $error - Reopening Socket"
+ ::cluster::sleep 2000
+ } else {
+ # Double the delay
+ ::cluster::sleep 250
+ }
+ }
+}
+
+###
+# topic: 963e24601d0dc61580c9727a74cdba67
+###
+proc ::cluster::cname rawname {
+ # Convert rawname to a canonical name
+ if {[string first @ $rawname] < 0 } {
+ return $rawname
+ }
+ lassign [split $rawname @] service host
+ if {$host eq {}} {
+ set host *
+ }
+ if {$host in {local localhost}} {
+ set host [::cluster::self]
+ }
+ return $service@$host
+}
+
+###
+# topic: 3f5f9e197cc9666dd7953d97fef34019
+###
+proc ::cluster::ipaddr macid {
+ # Convert rawname to a canonical name
+ if {$macid eq [::cluster::self]} {
+ return 127.0.0.1
+ }
+ foreach {servname dat} [search [cname *@$macid]] {
+ if {[dict exists $dat ipaddr]} {
+ return [dict get $dat ipaddr]
+ }
+ }
+ ###
+ # Do a lookup
+ ###
+ error "Could not locate $macid"
+}
+
+###
+# topic: e57db306f0e931d7febb5ad1f9cb2247
+###
+proc ::cluster::listen {} {
+ variable broadcast_sock
+ if {$broadcast_sock != {}} {
+ return $broadcast_sock
+ }
+ variable discovery_port
+ variable discovery_group
+ set broadcast_sock [udp_open $discovery_port reuse]
+ fconfigure $broadcast_sock -buffering none -blocking 0 \
+ -mcastadd $discovery_group \
+ -remote [list $discovery_group $discovery_port]
+ fileevent $broadcast_sock readable [list [namespace current]::UDPPacket $broadcast_sock]
+ ::cron::every cluster_heartbeat 30 ::cluster::heartbeat
+
+ return $broadcast_sock
+}
+
+###
+# topic: 2a33c825920162b0791e2cdae62e6164
+###
+proc ::cluster::UDPPacket sock {
+ variable ptpdata
+ set pid [pid]
+ set packet [string trim [read $sock]]
+ set peer [fconfigure $sock -peer]
+
+ if {![string is ascii $packet]} return
+ if {![::info complete $packet]} return
+
+ set sender [lindex $packet 0]
+ if {$::cluster::config(debug)} {
+ puts [list $::cluster::local_pid RECV $peer $packet]
+ }
+ if { $sender eq [pid] } {
+ # Ignore messages from myself
+ return
+ }
+
+ set messagetype [lindex $packet 1]
+ set messageinfo [lrange $packet 2 end]
+ switch -- [string toupper $messagetype] {
+ -SERVICE {
+ set serviceurl [lindex $messageinfo 0]
+ set serviceinfo [lindex $messageinfo 1]
+ dict set serviceinfo ipaddr [lindex $peer 0]
+ dict set serviceinfo closed 1
+ Service_Remove $serviceurl $serviceinfo
+ }
+ ~SERVICE {
+ set ::cluster::recv_message 1
+ set serviceurl [lindex $messageinfo 0]
+ set serviceinfo [lindex $messageinfo 1]
+ dict set serviceinfo ipaddr [lindex $peer 0]
+ Service_Modified $serviceurl $serviceinfo
+ set ::cluster::ping_recv($serviceurl) [clock seconds]
+ }
+ +SERVICE {
+ set ::cluster::recv_message 1
+ set serviceurl [lindex $messageinfo 0]
+ set serviceinfo [lindex $messageinfo 1]
+ dict set serviceinfo ipaddr [lindex $peer 0]
+ Service_Add $serviceurl $serviceinfo
+ set ::cluster::ping_recv($serviceurl) [clock seconds]
+ }
+ DISCOVERY {
+ variable config
+ ::cluster::heartbeat
+ if {$config(local_registry)==1} {
+ variable ptpdata
+ # A local registry barfs back all data that is sees
+ set now [clock seconds]
+ foreach {url info} [array get ptpdata] {
+ broadcast ~SERVICE $url $info
+ }
+ }
+ }
+ LOG {
+ set serviceurl [lindex $messageinfo 0]
+ set serviceinfo [lindex $messageinfo 1]
+ Service_Log $serviceurl $serviceinfo
+ }
+ ?WHOIS {
+ set wmacid [lindex $messageinfo 0]
+ if { $wmacid eq [::cluster::self] } {
+ broadcast +WHOIS [::cluster::self]
+ }
+ }
+ PONG {
+ set serviceurl [lindex $messageinfo 0]
+ set serviceinfo [lindex $messageinfo 1]
+ Service_Modified $serviceurl $serviceinfo
+ set ::cluster::ping_recv($serviceurl) [clock seconds]
+ }
+ PING {
+ set serviceurl [lindex $messageinfo 0]
+ foreach {url info} [search_local $serviceurl] {
+ broadcast PONG $url $info
+ }
+ }
+ }
+}
+
+proc ::cluster::ping {rawname} {
+ set rcpt [cname $rawname]
+ set ::cluster::ping_recv($rcpt) 0
+ set starttime [clock seconds]
+ set sleeptime 1
+ while 1 {
+ broadcast PING $rcpt
+ update
+ if {$::cluster::ping_recv($rcpt)} break
+ if {([clock seconds] - $starttime) > 120} {
+ error "Could not locate a local dispatch service"
+ }
+ sleep [incr sleeptime $sleeptime]
+ }
+}
+
+proc ::cluster::publish {url infodict} {
+ variable local_data
+ dict set infodict macid [self]
+ dict set infodict pid [pid]
+ set local_data($url) $infodict
+ broadcast +SERVICE $url $infodict
+}
+
+proc ::cluster::heartbeat {} {
+ variable ptpdata
+ variable config
+
+ set now [clock seconds]
+ foreach {item info} [array get ptpdata] {
+ set remove 0
+ if {[dict exists $info closed] && [dict get $info closed]} {
+ set remove 1
+ }
+ if {[dict exists $info updated] && ($now - [dict get $info updated])>$config(discovery_ttl)} {
+ set remove 1
+ }
+ if {$remove} {
+ Service_Remove $item $info
+ }
+ }
+ ###
+ # Broadcast the status of our local services
+ ###
+ variable local_data
+ foreach {url info} [array get local_data] {
+ broadcast ~SERVICE $url $info
+ }
+ ###
+ # Trigger any cluster events that haven't fired off
+ ###
+ foreach {eventid info} [array get ::cluster::events] {
+ if {$info eq "-1"} {
+ unset ::cluster::events($eventid)
+ } else {
+ lassign $info seconds ms
+ if {$seconds < $now} {
+ set ::cluster::events($eventid) -1
+ }
+ }
+ }
+}
+
+proc ::cluster::info url {
+ variable local_data
+ return [array get local_data $url]
+}
+
+proc ::cluster::unpublish {url infodict} {
+ variable local_data
+ foreach {field value} $infodict {
+ dict set local_data($url) $field $value
+ }
+ set info [lindex [array get local_data $url] 1]
+ broadcast -SERVICE $url $info
+ unset -nocomplain local_data($url)
+}
+
+proc ::cluster::configure {url infodict {send 1}} {
+ variable local_data
+ if {![::info exists local_data($url)]} return
+ foreach {field value} $infodict {
+ dict set local_data($url) $field $value
+ }
+ if {$send} {
+ broadcast ~SERVICE $url $local_data($url)
+ update
+ }
+}
+
+proc ::cluster::get_free_port {{startport 50000}} {
+ ::cluster::listen
+ ::cluster::broadcast DISCOVERY
+ after 10000 {set ::cluster::recv_message 0}
+ # Wait for a pingback or timeout
+ vwait ::cluster::recv_message
+ cluster::sleep 2000
+
+ set macid [::cluster::macid]
+ set port $startport
+ set conflict 1
+ while {$conflict} {
+ set conflict 0
+ set port [::nettool::find_port $port]
+ foreach {url info} [search *@[macid]] {
+ if {[dict exists $info port] && [dict get $info port] eq $port} {
+ incr port
+ set conflict 1
+ break
+ }
+ }
+ update
+ }
+ return $port
+}
+
+proc ::cluster::log args {
+ broadcast LOG {*}$args
+}
+
+proc ::cluster::LookUp {rawname} {
+ set self [self]
+ foreach {servname dat} [search [cname $rawname]] {
+ # Ignore services in the process of closing
+ if {[dict exists $dat macid] && [dict get $dat macid] eq $self} {
+ set ipaddr 127.0.0.1
+ } elseif {![dict exists $dat ipaddr]} {
+ set ipaddr [ipaddr [lindex [split $servname @] 1]]
+ } else {
+ set ipaddr [dict get $dat ipaddr]
+ }
+ if {![dict exists $dat port]} continue
+ if {[llength $ipaddr] > 1} {
+ ## Sort out which ipaddr is proper later
+ # for now take the last one
+ set ipaddr [lindex [dict get $dat ipaddr] end]
+ }
+ set port [dict get $dat port]
+ return [list $port $ipaddr]
+ }
+ return {}
+}
+
+###
+# topic: 2c04e58c7f93798f9a5ed31a7f5779ab
+###
+proc ::cluster::resolve {rawname} {
+ set result [LookUp $rawname]
+ if { $result ne {} } {
+ return $result
+ }
+ broadcast DISCOVERY
+ sleep 250
+ set result [LookUp $rawname]
+ if { $result ne {} } {
+ return $result
+ }
+ error "Could not locate $rawname"
+}
+
+###
+# topic: 6c7a0a3a8cb2a7ae98ff0dba960c37a7
+###
+proc ::cluster::pid {} {
+ variable local_pid
+ return $local_pid
+}
+
+proc ::cluster::macid {} {
+ variable local_macid
+ return $local_macid
+}
+
+proc ::cluster::self {} {
+ variable local_macid
+ return $local_macid
+}
+
+###
+# topic: f1b71ff12a8ac10373c67ac5d973cd81
+###
+proc ::cluster::send {service command args} {
+ set commid [resolve $service]
+ return [::comm::comm send $commid $command {*}$args]
+}
+
+proc ::cluster::throw {service command args} {
+ set commid [LookUp $service]
+ if { $commid eq {} } {
+ return
+ }
+ if [catch {::comm::comm send -async $commid $command {*}$args} reply] {
+ puts $stderr "ERR: SEND $service $reply"
+ }
+}
+
+proc ::cluster::sleep ms {
+ set eventid [incr ::cluster::eventcount]
+ set ::cluster::event($eventid) [list [clock seconds] [expr {[clock milliseconds]+$ms}]]
+ after $ms set ::cluster::event($eventid) -1
+ vwait ::cluster::event($eventid)
+}
+
+###
+# topic: c8475e832c912e962f238c61580b669e
+###
+proc ::cluster::search pattern {
+ set result {}
+ variable ptpdata
+ foreach {service dat} [array get ptpdata $pattern] {
+ foreach {field value} $dat {
+ dict set result $service $field $value
+ }
+ }
+ variable local_data
+ foreach {service dat} [array get local_data $pattern] {
+ foreach {field value} $dat {
+ dict set result $service $field $value
+ dict set result $service ipaddr 127.0.0.1
+ }
+ }
+ return $result
+}
+
+proc ::cluster::is_local pattern {
+ variable local_data
+ if {[array exists local_data $pattern]} {
+ return 1
+ }
+ if {[array exists local_data [cname $pattern]]} {
+ return 1
+ }
+ return 0
+}
+
+proc ::cluster::search_local pattern {
+ set result {}
+ variable local_data
+ foreach {service dat} [array get local_data $pattern] {
+ foreach {field value} $dat {
+ dict set result $service $field $value
+ }
+ }
+ return $result
+}
+
+proc ::cluster::Service_Add {serviceurl serviceinfo} {
+ # Code to register the presence of a service
+ if {[dict exists $serviceinfo pid] && [dict get $serviceinfo pid] eq [pid] } {
+ # Ignore attempts to overwrite locally managed services from the network
+ return
+ }
+ variable ptpdata
+ set ptpdata($serviceurl) $serviceinfo
+ dict set ptpdata($serviceurl) updated [clock seconds]
+}
+
+proc ::cluster::Service_Remove {serviceurl serviceinfo} {
+ # Code to register the loss of a service
+ if {[dict exists $serviceinfo pid] && [dict get $serviceinfo pid] eq [pid] } {
+ # Ignore attempts to overwrite locally managed services from the network
+ return
+ }
+ variable ptpdata
+ unset -nocomplain ptpdata($serviceurl)
+}
+
+proc ::cluster::Service_Modified {serviceurl serviceinfo} {
+ # Code to register an update to a service
+ if {[dict exists $serviceinfo pid] && [dict get $serviceinfo pid] eq [pid] } {
+ # Ignore attempts to overwrite locally managed services from the network
+ return
+ }
+ variable ptpdata
+ foreach {field value} $serviceinfo {
+ dict set ptpdata($serviceurl) $field $value
+ }
+ dict set ptpdata($serviceurl) updated [clock seconds]
+}
+
+proc ::cluster::Service_Log {service data} {
+ # Code to register an event
+}
+
+###
+# topic: d3e48e31cc4baf81395179f4097fee1b
+###
+namespace eval ::cluster {
+ # Number of seconds to "remember" data
+ variable config
+ array set config {
+ debug 0
+ discovery_ttl 300
+ local_registry 0
+ }
+ variable eventcount 0
+ variable cache {}
+ variable broadcast_sock {}
+ variable cache_maxage 500
+ variable discovery_port 38573
+ # Currently an unassigned group in the
+ # Local Network Control Block (224.0.0/24)
+ # See: RFC3692 and http://www.iana.org
+ variable discovery_group 224.0.0.200
+ variable local_port {}
+ variable local_macid [lindex [::nettool::mac_list] 0]
+ variable local_pid [::uuid::uuid generate]
+}
+
+package provide nameserv::cluster 0.2.3
diff --git a/tcllib/modules/nns/nns_cluster.test b/tcllib/modules/nns/nns_cluster.test
new file mode 100644
index 0000000..cdd4fac
--- /dev/null
+++ b/tcllib/modules/nns/nns_cluster.test
@@ -0,0 +1,195 @@
+# -*- tcl -*-
+# common.test: Tests for the common code of the name service
+#
+# Sourcing this file into Tcl runs the tests and generates output for
+# errors. No output means no errors were found.
+
+# -------------------------------------------------------------------------
+
+set testutilsscript [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+source $testutilsscript
+
+package require tcltest
+testsNeedTcl 8.5
+testsNeedTcltest 1.0
+
+set ::WHOAMI Main
+
+support {
+ use snit/snit2.tcl snit
+ use comm/comm.tcl comm
+ use dns/ip.tcl ip
+ use nettool/nettool.tcl nettool
+ use cron/cron.tcl cron
+ use uuid/uuid.tcl uuid
+ use interp/interp.tcl interp
+ use log/logger.tcl logger
+ use md5/md5x.tcl md5
+}
+testing {
+ useLocal nns_cluster.tcl nameserv::cluster
+}
+
+###
+# Create a server in a seperate interp
+###
+interp create server
+interp eval server [list set testutilsscript $testutilsscript]
+interp eval server {
+ source $testutilsscript
+ set ::WHOAMI Server
+
+ package require tcltest
+ testsNeedTcl 8.5
+ testsNeedTcltest 1.0
+
+ support {
+ use snit/snit2.tcl snit
+ use comm/comm.tcl comm
+ use dns/ip.tcl ip
+ use nettool/nettool.tcl nettool
+ use cron/cron.tcl cron
+ use uuid/uuid.tcl uuid
+ use interp/interp.tcl interp
+ use log/logger.tcl logger
+ use md5/md5x.tcl md5
+ }
+ testing {
+ use nns/nns_cluster.tcl nameserv::cluster
+ }
+ set ::cluster::local_pid SERVER
+ ::cluster::publish nns@[::cluster::macid] {}
+ update
+}
+set ::cluster::local_pid MAIN
+set macid [::cluster::macid]
+set myport [::nettool::allocate_port 10000]
+
+::cluster::ping nns@$macid
+set data [::cluster::search *]
+test cluster-comm-1.0 {Publish service - NNS} {
+ dict exists $data nns@[::cluster::macid]
+} {1}
+
+test cluster-comm-1.1 {Check that non-existant service does not exist} {
+ dict exists $data foo@bar
+} {0}
+
+###
+# Create a phony service
+###
+set now [clock seconds]
+::cluster::publish foo@bar [list clocktime $now]
+# The windows event loop needs a breather
+::cluster::ping nns@$macid
+
+set data [::cluster::search *]
+test cluster-comm-2.0 {Publish service - NNS} {
+ dict exists $data nns@[::cluster::macid]
+} {1}
+test cluster-comm-2.1 {Check that new service does exists} {
+ dict exists $data foo@bar
+} {1}
+
+###
+# Modify a service
+###
+::cluster::configure foo@bar {color pink}
+::cluster::ping nns@$macid
+
+set data [::cluster::search foo@bar]
+test cluster-comm-2.3 {Modify a service} {
+ dict get $data foo@bar color
+} {pink}
+
+::cluster::configure foo@bar {color blue}
+::cluster::ping nns@$macid
+
+set data [::cluster::search foo@bar]
+test cluster-comm-2.4 {Modify a service} {
+ dict get $data foo@bar color
+} {blue}
+
+
+###
+# Create another client in a seperate interp
+###
+interp create otherclient
+interp eval otherclient [list set testutilsscript $testutilsscript]
+interp eval otherclient {
+ source $testutilsscript
+ set ::WHOAMI Other
+
+ package require tcltest
+ testsNeedTcl 8
+ testsNeedTcltest 1.0
+
+ support {
+ use snit/snit2.tcl snit
+ use comm/comm.tcl comm
+ use dns/ip.tcl ip
+ use nettool/nettool.tcl nettool
+ use cron/cron.tcl cron
+ use uuid/uuid.tcl uuid
+ use interp/interp.tcl interp
+ use log/logger.tcl logger
+ use md5/md5x.tcl md5
+ }
+ testing {
+ use nns/nns_cluster.tcl nameserv::cluster
+ }
+
+ ###
+ # Cheat and let this server know the server is local
+ ###
+ set macid [::cluster::macid]
+ set myport [::nettool::allocate_port 10000]
+
+ set url other@$macid
+ ::comm::comm new $url -port $myport -local 0 -listen 1
+ ::cluster::publish $url [list port $myport protocol comm class comm]
+}
+::cluster::ping nns@$macid
+
+set data [::cluster::search *]
+test cluster-comm-3.0 {Publish service - NNS} {
+ dict exists $data nns@[::cluster::macid]
+} {1}
+test cluster-comm-3.1 {Check that new service does exists} {
+ dict exists $data foo@bar
+} {1}
+test cluster-comm-3.3 {Check that other service does exists} {
+ dict exists $data other@[::cluster::macid]
+} {1}
+
+test cluster-comm-3.3 {Check that other service does exists} {
+ set chan [::cluster::resolve other@[::cluster::macid]]
+ ::comm::comm send $chan {set foo b}
+} {b}
+
+###
+# Remove the phony service
+###
+::cluster::unpublish foo@bar {}
+::cluster::ping nns@$macid
+
+set data [::cluster::search *]
+test cluster-comm-4.0 {Publish service - NNS} {
+ dict exists $data nns@[::cluster::macid]
+} {1}
+test cluster-comm-4.1 {Check that service is closed} {
+ dict exists $data foo@bar
+} {0}
+
+###
+# Have a non-existant service fail
+###
+test cluster-comm-5.0 {Service lookup failture} {
+ catch {cluster::resolve foo@bar} pat
+} {1}
+#puts $pat
+
+testsuiteCleanup
+return
diff --git a/tcllib/modules/nns/nns_common.man b/tcllib/modules/nns/nns_common.man
new file mode 100644
index 0000000..8de8ca0
--- /dev/null
+++ b/tcllib/modules/nns/nns_common.man
@@ -0,0 +1,47 @@
+[manpage_begin nameserv::common n 0.1]
+[see_also nameserv::client(n)]
+[see_also nameserv::server(n)]
+[keywords client]
+[keywords {name service}]
+[keywords server]
+[copyright {2007-2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, shared definitions}]
+[category Networking]
+[require Tcl 8]
+[require nameserv::common [opt 0.1]]
+[description]
+
+Please read [term {Name service facility, introduction}] first.
+
+[para]
+
+This package is internal and of no interest to users. It provides the
+commands of the name service facility which are shared by the client
+and server implemented by the packages [package nameserv::server] and
+[package nameserv] (the client).
+
+[para]
+
+This service is built in top of and for the package [package comm].
+It has nothing to do with the Internet's Domain Name System. If the
+reader is looking for a package dealing with that please see Tcllib's
+packages [package dns] and [package resolv].
+
+[section API]
+
+The package exports a single command, as specified below:
+
+[list_begin definitions]
+
+[call [cmd ::nameserv::common::port]]
+
+The result returned by the command is the id of the default TCP/IP
+port a nameservice server will listen on, and a name service client
+will try to connect to.
+
+[list_end]
+
+[vset CATEGORY nameserv]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/nns/nns_intro.man b/tcllib/modules/nns/nns_intro.man
new file mode 100644
index 0000000..4ed3551
--- /dev/null
+++ b/tcllib/modules/nns/nns_intro.man
@@ -0,0 +1,128 @@
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin nns_intro n 1.0]
+[see_also nameserv(n)]
+[see_also nameserv::auto(n)]
+[see_also nameserv::common(n)]
+[see_also nameserv::protocol(n)]
+[see_also nameserv::server(n)]
+[see_also nnsd(n)]
+[see_also nss(n)]
+[keywords client]
+[keywords {name service}]
+[keywords server]
+[copyright {2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, introduction}]
+[category Networking]
+[description]
+[para]
+
+[term nns] (short for [emph {nano nameservice}]) is a facility built
+for the package [package comm], adding a simple name service to it.
+It is also built on top of [package comm], using it for the exchange
+of messages between the client and server parts.
+
+[para]
+
+This name service facility has nothing to do with the Internet's
+[term {Domain Name System}], otherwise known as [term DNS]. If the
+reader is looking for a package dealing with that please see either of
+the packages [package dns] and [package resolv], both found in Tcllib
+too.
+
+[para]
+
+Tcllib provides 2 applications and 4 packages which are working
+together and provide access to the facility at different levels.
+
+[section Applications]
+
+The application [syscmd nnsd] provides a simple name server which can
+be run by anybody anywhere on their system, as they see fit.
+
+It is also an example on the use of the server-side package
+[package nameserv::server].
+
+[para]
+
+Complementing this server is the [syscmd nns] client application.
+
+A possible, but no very sensible use would be to enter name/port
+bindings into a server from a shell script. Not sensible, as shell
+scripts normally do not provide a [package comm]-based service.
+
+[para]
+
+The only case for this to make some sense would be in a shell script
+wrapped around a Tcl script FOO which is using comm, to register the
+listening port used by FOO.
+
+However even there it would much more sensible to extend FOO to use
+the nameservice directly. And in regard on how to that [syscmd nns]
+can be used as both example and template.
+
+Beyond that it may also be useful to perform nameservice queries from
+shell scripts.
+
+[para]
+
+The third application, [syscmd nnslog] is a stripped down form of the
+[syscmd nns] client application. It is reduced to perform a continuous
+search for all changes and logs all received events to stdout.
+
+[para]
+
+Both clients use the [package nameserv::auto] package to automatically
+hande the loss and restoration of the connection to the server.
+
+[section Packages]
+
+The two main packages implementing the service are [package nameserv]
+and [package nameserv::server], i.e. client and server. The latter has
+not much of an API, just enough to start, stop, and configure it. See
+the application [syscmd nnsd] on how to use it.
+
+[para]
+
+The basic client, in package [package nameserv], provides the main API
+to manipulate and query the service. An example of its use is the
+application [syscmd nns].
+
+[para]
+
+The second client package, [package nameserv::auto] is API compatible
+to the basic client, but provides the additional functionality that it
+will automatically restore data like bound names when the connection
+to the name service was lost and then reestablished. I.e. it
+automatically detects the loss of the server and re-enters the data
+when the server comes back.
+
+[para]
+
+The package [package nameserv::common] is of no interest to users. It
+is an internal package containing code and definitions common to the
+packages [package nameserv] and [package nameserv::server].
+
+[para]
+
+All packages use the [package uevent] package for the reporting of
+special circumstances via events, and reserve the uevent-tag
+[term nameserv] for their exclusive use. All their events will be
+posted to that tag.
+
+[section Internals]
+
+The document [term {Name service facility, client/server protocol}]
+specifies the protocol used by the packages [package nameserv] and
+[package nameserv::server] to talk to each other. It is of no interest
+to users of either the packages or applications.
+
+[para]
+
+Developers wishing to modify and/or extend or to just understand the
+internals of the nameservice facility however are strongly advised to
+read it.
+
+[vset CATEGORY nameserv]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/nns/nns_protocol.man b/tcllib/modules/nns/nns_protocol.man
new file mode 100644
index 0000000..968de1f
--- /dev/null
+++ b/tcllib/modules/nns/nns_protocol.man
@@ -0,0 +1,182 @@
+[manpage_begin nameserv::protocol n 0.1]
+[see_also comm_wire(n)]
+[see_also nameserv(n)]
+[see_also nameserv::server(n)]
+[keywords comm]
+[keywords {name service}]
+[keywords protocol]
+[copyright {2007-2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, client/server protocol}]
+[category Networking]
+[description]
+
+The packages [package nameserv::server], [package nameserv], and
+[package nameserv::common] provide a simple unprotected name service
+facility for use in small trusted environments.
+
+[para]
+
+Please read [term {Name service facility, introduction}] first.
+
+[para]
+
+This document contains the specification of the network protocol which
+is used by client and server to talk to each other, enabling
+implementations of the same protocol in other languages.
+
+[section {Nano Name Service Protocol Version 1}]
+
+This protocol defines the basic set of messages to be supported by a
+name service, also called the [term Core] feature.
+
+[subsection {Basic Layer}]
+
+The basic communication between client and server is done using the
+remote-execution protocol specified by the Tcl package [package comm].
+The relevant document specifying its on-the-wire protocol can be found
+in [term comm_wire].
+
+[para]
+
+All the scripts exchanged via this protocol are single commands in
+list form and thus can be interpreted as plain messages instead of as
+Tcl commands. The commands/messages specified in the next section are
+the only commands understood by the server-side. Command and variable
+substitutions are not allowed within the messages, i.e. arguments have
+to be literal values.
+
+[para]
+
+The protocol is synchronous. I.e. for each message sent a response is
+expected, and has to be generated. All messages are sent by the client.
+The server does not sent messages, only responses to messages.
+
+[subsection {Message Layer}]
+
+[list_begin definitions]
+
+[call [method Bind] [arg name] [arg data]]
+
+The client sends this message when it registers itself at the service
+with a [arg name] and some associated [arg data]. The server has to
+send an error response if the [arg name] is already in use. Otherwise
+the response has to be an empty string.
+
+[para]
+
+The server has to accept multiple names for the same client.
+
+[call [method Release]]
+
+The client sends this message to unregister all names it is known
+under at the service. The response has to be an empty string, always.
+
+[call [method Search] [arg pattern]]
+
+The client sends this message to search the service for names matching
+the glob-[arg pattern]. The response has to be a dictionary containing
+the matching names as keys, and mapping them to the data associated
+with it at [method Bind]-time.
+
+[call [method ProtocolVersion]]
+
+The client sends this message to query the service for the highest
+version of the name service protocol it supports. The response has to
+be a positive integer number.
+
+[para]
+
+Servers supporting only [term {Nano Name Service Protocol Version 1}]
+have to return [const 1].
+
+[call [method ProtocolFeatures]]
+
+The client sends this message to query the service for the features of
+the name service protocol it supports. The response has to be a
+list containing feature names.
+
+[para]
+
+Servers supporting only [term {Nano Name Service Protocol Version 1}]
+have to return [const {{Core}}].
+
+[list_end]
+
+[section {Nano Name Service Protocol Extension: Continuous Search}]
+
+This protocol defines an extended set of messages to be supported by a
+name service, also called the [term Search/Continuous] feature. This
+feature defines additional messages between client and server, and is
+otherwise identical to version 1 of the protocol. See the last section
+for the details of our foundation.
+
+[para]
+
+A service supporting this feature has to put the feature name
+[const Search/Continuous] into the list of features returned by the
+message [term ProtocolFeatures].
+
+[para]
+
+For this extension the protocol is asynchronous. No direct response is
+expected for any of the messages in the extension. Furthermore the
+server will start sending messages on its own, instead of only
+responses to messages, and the client has to be able to handle these
+notifications.
+
+[list_begin definitions]
+
+[call [method Search/Continuous/Start] [arg tag] [arg pattern]]
+
+The client sends this message to start searching the service for names
+matching the glob-[arg pattern].
+
+In contrast to the regular [term Search] request this one asks the
+server to continuously monitor the database for the addition and
+removal of matching entries and to notify the client of all such
+changes. The particular search is identified by the [arg tag].
+
+[para]
+
+No direct response is expected, rather the clients expect to be
+notified of changes via explicit [term Search/Continuous/Result]
+messages generated by the service.
+
+[para]
+
+It is further expected that the [arg tag] information is passed
+unchanged to the [term Search/Continuous/Result] messages. This
+tagging of the results enables clients to start multiple searches and
+distinguish between the different results.
+
+[call [method Search/Continuous/Stop] [arg tag]]
+
+The client sends this message to stop the continuous search identified
+by the [arg tag].
+
+[call [method Search/Continuous/Change] [arg tag] [method add]|[method remove] [arg response]]
+
+This message is sent by the service to clients with active continuous
+searches to transfer found changes. The first such message for a new
+continuous search has to contains the current set of matching entries.
+
+[para]
+
+To ensure this a service has to generate an [method add]-message with
+an empty [arg response] if there were no matching entries at the time.
+
+[para]
+
+The [arg response] has to be a dictionary containing the matching
+names as keys, and mapping them to the data associated with it at
+[method Bind]-time.
+
+The argument coming before the response tells the client whether the
+names in the response were added or removed from the service.
+
+[list_end]
+
+[vset CATEGORY nameserv]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/nns/nns_server.man b/tcllib/modules/nns/nns_server.man
new file mode 100644
index 0000000..b62ec61
--- /dev/null
+++ b/tcllib/modules/nns/nns_server.man
@@ -0,0 +1,145 @@
+[manpage_begin nameserv::server n 0.3.2]
+[see_also nameserv::client(n)]
+[see_also nameserv::common(n)]
+[keywords {name service}]
+[keywords server]
+[copyright {2007-2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, Server}]
+[category Networking]
+[require Tcl 8.4]
+[require nameserv::server [opt 0.3.2]]
+[require comm]
+[require interp]
+[require logger]
+[description]
+
+Please read [term {Name service facility, introduction}] first.
+
+[para]
+
+This package provides an implementation of the serviver side of the
+name service facility queried by the client provided by the package
+[package nameserv]. All information required by the server will be
+held in memory. There is no persistent state.
+
+[para]
+
+This service is built in top of and for the package [package comm].
+It has nothing to do with the Internet's Domain Name System. If the
+reader is looking for a package dealing with that please see Tcllib's
+packages [package dns] and [package resolv].
+
+[para]
+
+This server supports the [term Core] protocol feature, and since
+version 0.3 the [term Search/Continuous] feature as well.
+
+[section API]
+
+The package exports five commands, as specified below:
+
+[list_begin definitions]
+
+[call [cmd ::nameserv::server::start]]
+
+This command starts the server and causes it to listen on the
+configured port. From now on clients are able to connect and make
+requests. The result of the command is the empty string.
+
+[para]
+
+Note that any incoming requests will only be handled if the
+application the server is part of does enter an event loop after this
+command has been run.
+
+[call [cmd ::nameserv::server::stop]]
+
+Invoking this command stops the server and releases all information it
+had. Existing connections are shut down, and no new connections will
+be accepted any longer. The result of the command is the empty string.
+
+[call [cmd ::nameserv::server::active?]]
+
+This command returns a boolean value indicating the state of the
+server. The result will be [const true] if the server is active,
+i.e. has been started, and [const false] otherwise.
+
+[call [cmd ::nameserv::server::cget] [option -option]]
+
+This command returns the currently configured value for the specified
+[option -option]. The list of supported options and their meaning can
+be found in section [sectref OPTIONS].
+
+[call [cmd ::nameserv::server::configure]]
+
+In this form the command returns a dictionary of all supported
+options, and their current values. The list of supported options and
+their meaning can be found in section [sectref OPTIONS].
+
+[call [cmd ::nameserv::server::configure] [option -option]]
+
+In this form the command is an alias for
+"[cmd ::nameserv::server::cget] [option -option]]".
+
+The list of supported options and their meaning can be found in
+section [sectref OPTIONS].
+
+[call [cmd ::nameserv::server::configure] "[option -option] [arg value]..."]
+
+In this form the command is used to configure one or more of the
+supported options. At least one option has to be specified, and each
+option is followed by its new value.
+
+The list of supported options and their meaning can be found in
+section [sectref OPTIONS].
+
+[para]
+
+This form can be used only if the server is not active, i.e. has not
+been started yet, or has been stopped. While the server is active it
+cannot be reconfigured.
+
+[list_end]
+
+[section OPTIONS]
+
+The options supported by the server are for the specification of the
+TCP port to listen on, and whether to accept non-local connections or
+not.
+
+They are:
+
+[list_begin options]
+[opt_def -localonly [arg bool]]
+
+This option specifies whether to accept only local connections
+(-localonly 1) or remote connections as well (-localonly 0). The
+default is to accept only local connections.
+
+[opt_def -port [arg number]]
+
+This option specifies the port the name service will listen on after
+it has been started. It has to be a positive integer number (> 0) not
+greater than 65536 (unsigned short). The initial default is the number
+returned by the command [cmd ::nameserv::server::common::port], as
+provided by the package [package ::nameserv::server::common].
+
+[list_end]
+
+[section HISTORY]
+[list_begin definitions]
+[def 0.3]
+Extended the server with the ability to perform asynchronous and
+continuous searches.
+
+[def 0.2]
+Changed name of -local switch to -localonly.
+
+[def 0.1]
+Initial implementation of the server.
+[list_end]
+
+[vset CATEGORY nameserv]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/nns/pkgIndex.tcl b/tcllib/modules/nns/pkgIndex.tcl
new file mode 100644
index 0000000..e51fba5
--- /dev/null
+++ b/tcllib/modules/nns/pkgIndex.tcl
@@ -0,0 +1,10 @@
+if {![package vsatisfies [package provide Tcl] 8]} {return}
+package ifneeded nameserv::common 0.1 [list source [file join $dir common.tcl]]
+
+if {![package vsatisfies [package provide Tcl] 8.4]} {return}
+package ifneeded nameserv 0.4.2 [list source [file join $dir nns.tcl]]
+package ifneeded nameserv::server 0.3.2 [list source [file join $dir server.tcl]]
+package ifneeded nameserv::auto 0.3 [list source [file join $dir nns_auto.tcl]]
+
+if {![package vsatisfies [package provide Tcl] 8.5]} {return}
+package ifneeded nameserv::cluster 0.2.3 [list source [file join $dir nns_cluster.tcl]]
diff --git a/tcllib/modules/nns/server.tcl b/tcllib/modules/nns/server.tcl
new file mode 100644
index 0000000..54ee688
--- /dev/null
+++ b/tcllib/modules/nns/server.tcl
@@ -0,0 +1,385 @@
+# -*- tcl -*-
+# ### ### ### ######### ######### #########
+## Name Service - Server (Singleton)
+
+# ### ### ### ######### ######### #########
+## Requirements
+
+package require Tcl 8.4
+package require comm ; # Generic message transport
+package require interp ; # Interpreter helpers.
+package require logger ; # Tracing internal activity
+package require nameserv::common ; # Common/shared utilities
+
+namespace eval ::nameserv::server {}
+
+# ### ### ### ######### ######### #########
+## API: Start, Stop
+
+proc ::nameserv::server::start {} {
+ variable comm
+ variable port
+ variable localonly
+
+ log::debug "start"
+ if {$comm ne ""} return
+
+ log::debug "start /granted"
+
+ set interp [interp::createEmpty]
+ foreach msg {
+ Bind
+ Release
+ Search
+ Search/Continuous/Start
+ Search/Continuous/Stop
+ ProtocolVersion
+ ProtocolFeatures
+ } {
+ interp alias $interp $msg {} ::nameserv::server::$msg
+ }
+
+ set comm [comm::comm new ::nameserv::server::COMM \
+ -interp $interp \
+ -port $port \
+ -listen 1 \
+ -local $localonly]
+
+ $comm hook lost ::nameserv::server::LOST
+
+ log::debug "UP @$port local-only $localonly"
+ return
+}
+
+proc ::nameserv::server::stop {} {
+ variable comm
+ variable names
+ variable data
+
+ log::debug "stop"
+ if {$comm eq ""} return
+
+ log::debug "stop /granted"
+
+ # This kills all existing connection and destroys the configured
+ # -interp as well.
+
+ $comm destroy
+ set comm ""
+
+ array unset names *
+ array unset data *
+
+ log::debug "DOWN"
+ return
+}
+
+proc ::nameserv::server::active? {} {
+ variable comm
+ return [expr {$comm ne ""}]
+}
+
+# ### ### ### ######### ######### #########
+## INT: Protocol operations
+
+proc ::nameserv::server::ProtocolVersion {} {return 1}
+proc ::nameserv::server::ProtocolFeatures {} {return {Core Search/Continuous}}
+
+proc ::nameserv::server::Bind {name cdata} {
+ variable comm
+ variable names
+ variable data
+
+ set id [$comm remoteid]
+
+ log::debug "bind ([list $name -> $cdata]), for $id"
+
+ if {[info exists data($name)]} {
+ log::debug "bind failed, \"$name\" is already bound"
+ return -code error "Name \"$name\" is already bound"
+ }
+
+ lappend names($id) $name
+ set data($name) $cdata
+
+ Search/Continuous/NotifyAdd $name $cdata
+ return
+}
+
+proc ::nameserv::server::Release {} {
+ variable comm
+ ReleaseId [$comm remoteid]
+ return
+}
+
+proc ::nameserv::server::Search {pattern} {
+ variable data
+ return [array get data $pattern]
+}
+
+proc ::nameserv::server::ReleaseId {id} {
+ variable names
+ variable data
+ variable searchi
+
+ log::debug "release id $id"
+
+ # Two steps. Release all searches the client may have open, then
+ # all names it may have bound. That last step may trigger
+ # notifications for searches by other clients. It must not trigger
+ # searches from the client just going away, hence their release
+ # first.
+
+ foreach k [array names searchi [list $id *]] {
+ Search/Release $k
+ }
+
+ if {[info exists names($id)]} {
+ set gone {}
+ foreach n $names($id) {
+ lappend gone $n $data($n)
+ catch {unset data($n)}
+
+ log::debug "release name <$n>"
+ }
+ unset names($id)
+
+ Search/Continuous/NotifyRelease $gone
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Support for continuous and async searches
+
+proc ::nameserv::server::Search/Continuous/Start {tag pattern} {
+ variable data
+ variable searchi
+ variable searchp
+ variable comm
+
+ set id [$comm remoteid]
+
+ # Register the search, then generate the initial response.
+ # Non-unique tags are silently discarded. Clients will wait
+ # forever.
+
+ set k [list $id $tag]
+
+ log::debug "search <$k>"
+
+ if {[info exists searchi($k)]} {
+ log::debug "search already known"
+ return
+ }
+
+ log::debug "search added"
+
+ set searchi($k) $pattern
+ lappend searchp($pattern) $k
+
+ $comm send -async $id [list Search/Continuous/Change \
+ $tag add [array get data $pattern]]
+ return
+}
+
+proc ::nameserv::server::Search/Continuous/Stop {tag} {
+ Search/Release [list [$comm remoteid] $tag]
+ return
+}
+
+proc ::nameserv::server::Search/Release {k} {
+ variable searchi
+ variable searchp
+
+ # Remove search information from the data store
+
+ if {![info exists searchi($k)]} return
+
+ log::debug "release search <$k>"
+
+ set pattern $searchi($k)
+ unset searchi($k)
+
+ set pos [lsearch -exact $searchp($pattern) $k]
+ if {$pos < 0} return
+ set new [lreplace $searchp($pattern) $pos $pos]
+ if {[llength $new]} {
+ # Shorten the callback list.
+ set searchp($pattern) $new
+ } else {
+ # Nothing monitors that pattern anymore, remove it completely.
+ unset searchp($pattern)
+ }
+ return
+}
+
+proc ::nameserv::server::Search/Continuous/NotifyAdd {name val} {
+ variable searchp
+
+ # Abort quickly if there are no searches waiting.
+ if {![array size searchp]} return
+
+ foreach p [array names searchp] {
+ if {![string match $p $name]} continue
+ Notify $p add [list $name $val]
+ }
+ return
+}
+
+proc ::nameserv::server::Search/Continuous/NotifyRelease {gone} {
+ variable searchp
+
+ # Abort quickly if there are no searches waiting.
+ if {![array size searchp]} return
+
+ array set m $gone
+ foreach p [array names searchp] {
+ set response [array get m $p]
+ if {![llength $response]} continue
+ Notify $p remove $response
+ }
+ return
+}
+
+proc ::nameserv::server::Notify {p type response} {
+ variable searchp
+ variable comm
+
+ foreach item $searchp($p) {
+ foreach {id tag} $item break
+ $comm send -async $id \
+ [list Search/Continuous/Change $tag $type $response]
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Initialization - In-memory database
+
+namespace eval ::nameserv::server {
+ # Database
+ # search = list (id tag) : Searches are identified by client and a tag.
+ #
+ # array (id -> list (name)) : Names under which a connection is known.
+ # array (name -> data) : Data associated with a name.
+ #
+ # array (pattern -> list (search)) : Per pattern the list of searches using it.
+ # array (search -> pattern) : Pattern per active search.
+ #
+ # searchp <~~> names
+ # searchi <~~> data
+
+ variable names ; array set names {}
+ variable data ; array set data {}
+ variable searchp ; array set searchp {}
+ variable searchi ; array set searchi {}
+}
+
+# ### ### ### ######### ######### #########
+## INT: Connection management
+
+proc ::nameserv::server::LOST {args} {
+ # Currently just to see when a client goes away.
+
+ upvar 1 id id chan chan reason reason
+ ReleaseId $id
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Initialization - System state
+
+namespace eval ::nameserv::server {
+ # Object command of the communication channel of the server.
+ # If present re-configuration is not possible.
+
+ variable comm {}
+}
+
+# ### ### ### ######### ######### #########
+## API: Configuration management (host, port)
+
+proc ::nameserv::server::cget {option} {
+ return [configure $option]
+}
+
+proc ::nameserv::server::configure {args} {
+ variable localonly
+ variable port
+ variable comm
+
+ if {![llength $args]} {
+ return [list -localonly $localonly -port $port]
+ }
+ if {[llength $args] == 1} {
+ # cget
+ set opt [lindex $args 0]
+ switch -exact -- $opt {
+ -localonly { return $localonly }
+ -port { return $port }
+ default {
+ return -code error "bad option \"$opt\", expected -localonly, or -port"
+ }
+ }
+ }
+
+ # Note: Should -port be made configurable after communication has
+ # started it might be necessary to provide code to re-initialize
+ # the connections to all known clients using the new
+ # configuration.
+
+ while {[llength $args]} {
+ set opt [lindex $args 0]
+ switch -exact -- $opt {
+ -localonly {
+ if {[llength $args] < 2} {
+ return -code error "value for \"$opt\" is missing"
+ }
+ # Todo: Check boolean
+ set new [lindex $args 1]
+ set args [lrange $args 2 end]
+
+ if {$new == $localonly} continue
+ set localonly $new
+ if {$comm eq ""} continue
+ $comm configure -local $localonly
+ }
+ -port {
+ if {$comm ne ""} {
+ return -code error "Unable to configure an active server"
+ }
+ if {[llength $args] < 2} {
+ return -code error "value for \"$opt\" is missing"
+ }
+ # Todo: Check non-zero unsigned short integer
+ set port [lindex $args 1]
+ set args [lrange $args 2 end]
+ }
+ default {
+ return -code error "bad option \"$opt\", expected -localonly, or -port"
+ }
+ }
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Initialization - Tracing, Configuration
+
+logger::initNamespace ::nameserv::server
+namespace eval ::nameserv::server {
+ # Port the server will listen on, and boolean flag determining
+ # acceptance of non-local connections.
+
+ variable port [nameserv::common::port]
+ variable localonly 1
+}
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide nameserv::server 0.3.2
+
+##
+# ### ### ### ######### ######### #########