Check-in [01b7d7cc94]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:heavy refactoring in topcua incl. beginning of a nodeset loader
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 01b7d7cc94bf8c9ed325f41720538ef882fbeb5f
User & Date: chw 2020-08-02 16:54:17
Original Comment: heave refactoring in topcua incl. beginning of a nodeset loader
Context
2020-08-03
05:51
jsmpeg changes due to ticket [014f736b43] check-in: 15fc2fdc12 user: chw tags: trunk
2020-08-02
16:54
heavy refactoring in topcua incl. beginning of a nodeset loader check-in: 01b7d7cc94 user: chw tags: trunk
2020-07-30
04:58
more changes in topcua for type handling check-in: 416a0ae7c1 user: chw tags: trunk
Changes

Changes to jni/topcua/doc/opcua.n.

310
311
312
313
314
315
316
317
318
319

320
321
322


323
324
325
326
327
328
329
330
...
765
766
767
768
769
770
771








772
773
774
775
776
777
778
779
780


781
782
783
784
785
786
787
788
789
...
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
of the reference between the parent and the new node. \fIBrname\fR is
the browse name (see section \fBQUALIFIED NAMES\fR) of the new node.
The optional \fIattrs\fR parameter specifies attributes for the new node
in form of a dictionary (see \fBopcua attr default\fR). If it is omitted,
default values are used. The \fIDisplayName\fR attribute if left empty
is preset to the name part of the browse name parameter.
.TP
\fBopcua add\fR \fIhandle\fR \fBMethod\fR \fInodeid parent reftype outargs brname inargs cmd\fR ?\fIattrs\fR?
.
Adds an new node of node class \fIMethod\fR in the server object \fIhandle\fR

and returns the node identifer. The parameter \fIoutargs\fR describes the
output arguments of the method as a list of zero or more pairs of data type
and argument name. Likewise, \fIinargs\fR describes the input arguments of


the method. The parameter \fIcmd\fR is the Tcl callback to handle the
method invocation, see section \fBMETHOD CALLBACKS\fR for more information.
For the other parameters, refer to \fBopcua add DataType\fR.
.TP
\fBopcua add\fR \fIhandle\fR \fBNamespace\fR \fIname\fR
.
Adds the new namespace \fIname\fR to the server object \fIhandle\fR and
returns a numeric identifier for this namespace.
................................................................................
.SH "QUALIFIED NAMES"
.PP
Qualified names are used for example in the \fBopcua browse\fR and
\fBopcua translate\fR operations as so called browse names. These
are made up of an optional numeric namespace prefix (a number followed
by a colon) and a name, e.g. \fI2:MyObject\fR. The namespace prefix is
left out if the name refers to namespace zero.








.SH "SUPPORTED DATA TYPES"
.PP
Currently, most of the data types of namespace zero are supported
and can be mapped to/from Tcl, i.e. integral and floating point numbers,
strings, GUIDs, and interal extension objects (similar to structures).
For the latter, dictionaries are used in both directions, i.e. for
encoding, a dictionary is searched for the respective member names,
for decoding, a dictionary is created from the internal representation
using the member names of the data type, see \fBopcua attrs default\fR


for example. Support for custom data types is highly experimental and
underdocumented (see \fBopcua gentypes\fR).
.SH "MONITOR CALLBACKS"
.PP
Monitor callbacks are invoked when a monitored item (data or event)
is received. The callback parameter given in \fBopcua monitor new\fR
must have proper list format and gets a single value (data) or
a list of values (event) appended prior to invocation.
.SH "DATA SOURCE CALLBACKS"
................................................................................
set OF [lindex [opcua translate S [opcua root] / Objects] 0]

# create an object in our namespace in Objects folder
set obj [opcua add S Object "ns=$ns;s=MyObject" $OF Organizes \\
             "$ns:MyObject"]

# create methods on object
set meth [opcua add S Method "ns=$ns;s=Reverse" \\
              $obj HasComponent \\
              {String out} "$ns:Reverse" {String in} \\
              ::opcua::S::_reverse]
set meth [opcua add S Method "ns=$ns;s=WordSplit" \\
              $obj HasComponent \\
              {String out} "$ns:WordSplit" {String in} \\
              ::opcua::S::_wordsplit]
set meth [opcua add S Method "ns=$ns;s=Exit" \\
              $obj HasComponent \\
              {} "$ns:Exit" {} \\
              ::opcua::S::_exit]

# create a variable in our namespace in Objects folder
set var [opcua add S Variable "ns=$ns;s=ItsTclTime" \\
              $OF Organizes \\







|


>
|
|
|
>
>
|







 







>
>
>
>
>
>
>
>









>
>
|
|







 







|



|



|







310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
...
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
...
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
of the reference between the parent and the new node. \fIBrname\fR is
the browse name (see section \fBQUALIFIED NAMES\fR) of the new node.
The optional \fIattrs\fR parameter specifies attributes for the new node
in form of a dictionary (see \fBopcua attr default\fR). If it is omitted,
default values are used. The \fIDisplayName\fR attribute if left empty
is preset to the name part of the browse name parameter.
.TP
\fBopcua add\fR \fIhandle\fR \fBMethod|SimpleMethod\fR \fInodeid parent reftype outargs brname inargs cmd\fR ?\fIattrs\fR?
.
Adds an new node of node class \fIMethod\fR in the server object \fIhandle\fR
and returns the node identifer. In the \fBSimpleMethod\fR command form the
parameter \fIoutargs\fR describes the output arguments of the method as a
list of zero or more pairs of data type and argument names. Likewise,
\fIinargs\fR describes the input arguments of the method. In the \fBMethod\fR
command form, both \fIoutargs\fR and \fIinargs\fR must be provided as lists
of dicts with the template obtained from \fBopcua types empty\fR
\fIArgument\fR. The parameter \fIcmd\fR is the Tcl callback to handle the
method invocation, see section \fBMETHOD CALLBACKS\fR for more information.
For the other parameters, refer to \fBopcua add DataType\fR.
.TP
\fBopcua add\fR \fIhandle\fR \fBNamespace\fR \fIname\fR
.
Adds the new namespace \fIname\fR to the server object \fIhandle\fR and
returns a numeric identifier for this namespace.
................................................................................
.SH "QUALIFIED NAMES"
.PP
Qualified names are used for example in the \fBopcua browse\fR and
\fBopcua translate\fR operations as so called browse names. These
are made up of an optional numeric namespace prefix (a number followed
by a colon) and a name, e.g. \fI2:MyObject\fR. The namespace prefix is
left out if the name refers to namespace zero.
.SH "LOCALIZED TEXT"
.PP
The data type \fBLocalizedText\fR is represented as a dict with the
keys \fIlocale\fR and \fItext\fR. When converting a Tcl value to
a \fBLocalizedText\fR item, these keys are tried to be associated.
If this operation fails, the Tcl value is used as the \fItext\fR
part of the \fBLocalizedText\fR item and the \fIlocale\fR part is
left empty.
.SH "SUPPORTED DATA TYPES"
.PP
Currently, most of the data types of namespace zero are supported
and can be mapped to/from Tcl, i.e. integral and floating point numbers,
strings, GUIDs, and interal extension objects (similar to structures).
For the latter, dictionaries are used in both directions, i.e. for
encoding, a dictionary is searched for the respective member names,
for decoding, a dictionary is created from the internal representation
using the member names of the data type, see \fBopcua attrs default\fR
for example. To represent arrays, Tcl lists are used, e.g. an array
of structures will yield a list of dicts when expressed as Tcl value.
Support for custom data types is highly experimental and underdocumented
(see \fBopcua gentypes\fR).
.SH "MONITOR CALLBACKS"
.PP
Monitor callbacks are invoked when a monitored item (data or event)
is received. The callback parameter given in \fBopcua monitor new\fR
must have proper list format and gets a single value (data) or
a list of values (event) appended prior to invocation.
.SH "DATA SOURCE CALLBACKS"
................................................................................
set OF [lindex [opcua translate S [opcua root] / Objects] 0]

# create an object in our namespace in Objects folder
set obj [opcua add S Object "ns=$ns;s=MyObject" $OF Organizes \\
             "$ns:MyObject"]

# create methods on object
set meth [opcua add S SimpleMethod "ns=$ns;s=Reverse" \\
              $obj HasComponent \\
              {String out} "$ns:Reverse" {String in} \\
              ::opcua::S::_reverse]
set meth [opcua add S SimpleMethod "ns=$ns;s=WordSplit" \\
              $obj HasComponent \\
              {String out} "$ns:WordSplit" {String in} \\
              ::opcua::S::_wordsplit]
set meth [opcua add S SimpleMethod "ns=$ns;s=Exit" \\
              $obj HasComponent \\
              {} "$ns:Exit" {} \\
              ::opcua::S::_exit]

# create a variable in our namespace in Objects folder
set var [opcua add S Variable "ns=$ns;s=ItsTclTime" \\
              $OF Organizes \\

Changes to jni/topcua/examples/server.tcl.

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# get Objects folder
set OF [lindex [opcua translate S [opcua root] / Objects] 0]

# create an object in our namespace in Objects folder
set obj [opcua add S Object "ns=$ns;s=MyObject" $OF Organizes "$ns:MyObject"]

# create methods on object
set meth [opcua add S Method "ns=$ns;s=Reverse" $obj HasComponent \
	      {String out} "$ns:Reverse" {String in} ::opcua::S::_reverse]
set meth [opcua add S Method "ns=$ns;s=WordSplit" $obj HasComponent \
	      {String out} "$ns:WordSplit" {String in} ::opcua::S::_wordsplit]
set meth [opcua add S Method "ns=$ns;s=Exit" $obj HasComponent \
	      {} "$ns:Exit" {} ::opcua::S::_exit]

# create a variable in our namespace in Objects folder
set var [opcua add S Variable "ns=$ns;s=ItsTclTime" $OF Organizes \
	     "$ns:ItsTclTime" {} {} ::opcua::S::_its_tcl_time]

# dump methods







|

|

|







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# get Objects folder
set OF [lindex [opcua translate S [opcua root] / Objects] 0]

# create an object in our namespace in Objects folder
set obj [opcua add S Object "ns=$ns;s=MyObject" $OF Organizes "$ns:MyObject"]

# create methods on object
set meth [opcua add S SimpleMethod "ns=$ns;s=Reverse" $obj HasComponent \
	      {String out} "$ns:Reverse" {String in} ::opcua::S::_reverse]
set meth [opcua add S SimpleMethod "ns=$ns;s=WordSplit" $obj HasComponent \
	      {String out} "$ns:WordSplit" {String in} ::opcua::S::_wordsplit]
set meth [opcua add S SimpleMethod "ns=$ns;s=Exit" $obj HasComponent \
	      {} "$ns:Exit" {} ::opcua::S::_exit]

# create a variable in our namespace in Objects folder
set var [opcua add S Variable "ns=$ns;s=ItsTclTime" $OF Organizes \
	     "$ns:ItsTclTime" {} {} ::opcua::S::_its_tcl_time]

# dump methods

Changes to jni/topcua/examples/server_sec.tcl.

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# get Objects folder
set OF [lindex [opcua translate S [opcua root] / Objects] 0]

# create an object in our namespace in Objects folder
set obj [opcua add S Object "ns=$ns;s=MyObject" $OF Organizes "$ns:MyObject"]

# create methods on object
set meth [opcua add S Method "ns=$ns;s=Reverse" $obj HasComponent \
	      {String out} "$ns:Reverse" {String in} ::opcua::S::_reverse]
set meth [opcua add S Method "ns=$ns;s=WordSplit" $obj HasComponent \
	      {String out} "$ns:WordSplit" {String in} ::opcua::S::_wordsplit]
set meth [opcua add S Method "ns=$ns;s=Exit" $obj HasComponent \
	      {} "$ns:Exit" {} ::opcua::S::_exit]

# create a variable in our namespace in Objects folder
set var [opcua add S Variable "ns=$ns;s=ItsTclTime" $OF Organizes \
	     "$ns:ItsTclTime" {} {} ::opcua::S::_its_tcl_time]

# dump methods







|

|

|







67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# get Objects folder
set OF [lindex [opcua translate S [opcua root] / Objects] 0]

# create an object in our namespace in Objects folder
set obj [opcua add S Object "ns=$ns;s=MyObject" $OF Organizes "$ns:MyObject"]

# create methods on object
set meth [opcua add S SimpleMethod "ns=$ns;s=Reverse" $obj HasComponent \
	      {String out} "$ns:Reverse" {String in} ::opcua::S::_reverse]
set meth [opcua add S SimpleMethod "ns=$ns;s=WordSplit" $obj HasComponent \
	      {String out} "$ns:WordSplit" {String in} ::opcua::S::_wordsplit]
set meth [opcua add S SimpleMethod "ns=$ns;s=Exit" $obj HasComponent \
	      {} "$ns:Exit" {} ::opcua::S::_exit]

# create a variable in our namespace in Objects folder
set var [opcua add S Variable "ns=$ns;s=ItsTclTime" $OF Organizes \
	     "$ns:ItsTclTime" {} {} ::opcua::S::_its_tcl_time]

# dump methods

Changes to jni/topcua/examples/server_types.tcl.

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
set OF [lindex [opcua translate S [opcua root] / Objects] 0]

# create an object in our namespace in Objects folder
set obj [opcua add S Object "ns=${ns};s=TclIsCool" $OF Organizes \
	    "${ns}:TclIsCool"]

# create method on object
set meth [opcua add S Method "ns=${ns};s=GetPlatform" $obj HasComponent \
	    {KVPair out} "${ns}:GetPlatform" {} ::opcua::S::_platform]

# create a variable with platform data
set att [opcua attrs default VariableAttributes]
if {[opcua version] < 1.0} {
    dict set att DataType [opcua types nodeid S KVPair]
    dict set att Value [concat KVPair [opcua::S::_platform {} {}]]







|







99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
set OF [lindex [opcua translate S [opcua root] / Objects] 0]

# create an object in our namespace in Objects folder
set obj [opcua add S Object "ns=${ns};s=TclIsCool" $OF Organizes \
	    "${ns}:TclIsCool"]

# create method on object
set meth [opcua add S SimpleMethod "ns=${ns};s=GetPlatform" $obj HasComponent \
	    {KVPair out} "${ns}:GetPlatform" {} ::opcua::S::_platform]

# create a variable with platform data
set att [opcua attrs default VariableAttributes]
if {[opcua version] < 1.0} {
    dict set att DataType [opcua types nodeid S KVPair]
    dict set att Value [concat KVPair [opcua::S::_platform {} {}]]

Changes to jni/topcua/examples/structs2.tcl.

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
	return $rgb
    }
}

# make an object with some methods
set obj [opcua add S Object "ns=${nsidx};s=MyObject" $OF \
	     Organizes "${nsidx}:MyObject"]
set meth [opcua add S Method "ns=${nsidx};s=platform" $obj HasComponent \
	      {KVPair out} "${nsidx}:platform" {} \
	      ::opcua::S::_platform]
set meth [opcua add S Method "ns=${nsidx};s=flip_red_blue" $obj HasComponent \
	      {RGB out} "${nsidx}:flip_red_blue" {RGB in} \
	      ::opcua::S::_flip_red_blue]

# generate stubs
opcua genstubs S /Root/Objects/${nsidx}:MyObject/${nsidx}:

# local test
set var "ns=${nsidx};s=X1"







|
|

|
|







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
	return $rgb
    }
}

# make an object with some methods
set obj [opcua add S Object "ns=${nsidx};s=MyObject" $OF \
	     Organizes "${nsidx}:MyObject"]
set meth [opcua add S SimpleMethod "ns=${nsidx};s=platform" $obj \
	      HasComponent {KVPair out} "${nsidx}:platform" {} \
	      ::opcua::S::_platform]
set meth [opcua add S SimpleMethod "ns=${nsidx};s=flip_red_blue" $obj \
	      HasComponent {RGB out} "${nsidx}:flip_red_blue" {RGB in} \
	      ::opcua::S::_flip_red_blue]

# generate stubs
opcua genstubs S /Root/Objects/${nsidx}:MyObject/${nsidx}:

# local test
set var "ns=${nsidx};s=X1"

Changes to jni/topcua/library/topcua.tcl.

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
...
223
224
225
226
227
228
229
230






231
232
233
234
235
236
237
...
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
...
336
337
338
339
340
341
342
343








344
345
346
347
348
349
350
...
524
525
526
527
528
529
530
531



532
533















534
535
536
537
538
539
540
...
569
570
571
572
573
574
575




576
577
578
579
580
581
582
...
590
591
592
593
594
595
596




597
598
599
600
601
602
603
...
773
774
775
776
777
778
779
780




















































































































































































































































































































































































































































































































































































































































































































































































































781
782
783
784
785

786
787
788
789
	    }
	}
	# keep only the nodeid
	set type [lindex $type 0]
	# read BrowseName attribute yields QualifiedName
	set bname [read $handle $root BrowseName]
	# read DisplayName attribute yields LocalizedText
	# but we want the name only here
	set dname [lindex [read $handle $root DisplayName] 1]
	_walk $handle $root $bname $dname Object $org $type ret
	# remove duplicates by path
	array set x {}
	set nodup {}
	foreach {l n b d c r t path} $ret {
	    if {[::info exists x($path)]} {
		continue
................................................................................
		    set ia [read $handle $ia]
		} else {
		    # no arguments
		    set ia {}
		}
		# go over the arguments
		foreach in $ia {
		    lassign $in typeid name






		    lappend plist $name
		    # caution: type name can have a namespace index
		    set type [read $handle $typeid BrowseName]
		    if {[string match "*:*" $type]} {
			set short \
			    [string range $type [string first ":" $type]+1 end]
			if {$short in [types list $handle]} {
................................................................................
	    set doc [dom parse $string]
	}]} {
	    return $out
	}
	set root [$doc documentElement]
	# process namespaces
	array set ns {}
	foreach attr [$root attributes] {
	    if {[llength $attr] == 3} {
		lassign $attr loc pfx uri
	    }
	    if {$uri ne ""} continue
	    set nsx "xmlns:$loc"
	    if {[$root hasAttribute $nsx]} {
		set ns($pfx) [$root getAttribute $nsx]
	    }
	}
................................................................................
		    continue
		}
		if {[$field hasAttribute SwitchField]} {
		    # cannot process this struct
		    set stf {}
		    break
		}
		# TBD: handle attribute LengthField
		if {![$field hasAttribute TypeName]} {
		    # cannot process this struct
		    set stf {}
		    break
		}
		set nstype [$field getAttribute TypeName]
		lassign [split $nstype :] fns type
................................................................................
		    lappend stf $nstype
		}
		if {![$field hasAttribute Name]} {
		    # cannot process this struct
		    set stf {}
		    break
		}
		lappend stf [$field getAttribute Name]








	    }
	    if {[llength $stf] && [$struct hasAttribute Name]} {
		set st([$struct getAttribute Name]) $stf
	    }
	}
	# structs need further checking later
	set stl [array names st]
................................................................................
	    }
	}
	foreach n $todo {
	    _resolve $n have need later defs
	}
    }

    # Generate ExtensionObject defs for enums and structs




    proc gentypes {handle} {















	foreach {name nodeid encid bsdname} [_getstructs $handle] {
	    set stn($name) $nodeid
	    set enc($name) $encid
	    set bsn($bsdname) $name
	}
	if {![array exists stn]} {
	    return
................................................................................
		set id [lindex $id 0]
		if {[catch {
		    translate $handle $id HasEncoding {Default Binary}
		} eid]} {
		    set eid $id
		}
		set eid [lindex $eid 0]




		lappend defs [list typedef $handle enum \
				  $en $id $eid $type \
				  {*}[lrange $ed 1 end]]
	    }
	    foreach {sn sd} [dict get $data structs] {
		if {![::info exists bsn($sn)]} {
		    # no mapping from *.bsd to address space
................................................................................
		    # verify namespace index
		    if {[scan $stn($sn) "ns=%d;i=%d" ns id] != 2} {
			continue
		    }
		    if {$ns != $nsindex} {
			continue
		    }




		}
		set def [list typedef $handle struct $sn $stn($bn) $enc($bn)]
		foreach {type member} $sd {
		    if {[regsub -- ^tns: $type ${nsindex}: type]} {
			# enums have two elements
			scan [lindex $type 0] "%d:%s" tns ttype
			if {![::info exists have($ttype)]} {
................................................................................
	    # add reference to encoding object
	    add $handle Reference $te HasDescription $tx 1
	}
	append bsd "</opc:TypeDictionary>\n"
	# store *.bsd type defs into node
	write $handle $tt ByteString $bsd
    }





















































































































































































































































































































































































































































































































































































































































































































































































































    # Make some procs visible in opcua ensemble.

    apply [list ns {
	set cmds [::namespace ensemble configure $ns -subcommands]
	lappend cmds tree ptree children parent genstubs gentypes deftypes

	::namespace ensemble configure $ns -subcommands $cmds
    } [::namespace current]] [::namespace current]

}







|
|







 







|
>
>
>
>
>
>







 







|
|
|







 







<







 







|
>
>
>
>
>
>
>
>







 







|
>
>
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>







 







>
>
>
>







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





>




73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
...
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
...
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
...
312
313
314
315
316
317
318

319
320
321
322
323
324
325
...
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
...
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
...
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
...
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
...
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
	    }
	}
	# keep only the nodeid
	set type [lindex $type 0]
	# read BrowseName attribute yields QualifiedName
	set bname [read $handle $root BrowseName]
	# read DisplayName attribute yields LocalizedText
	# but we want the text part only here
	set dname [dict get [read $handle $root DisplayName] text]
	_walk $handle $root $bname $dname Object $org $type ret
	# remove duplicates by path
	array set x {}
	set nodup {}
	foreach {l n b d c r t path} $ret {
	    if {[::info exists x($path)]} {
		continue
................................................................................
		    set ia [read $handle $ia]
		} else {
		    # no arguments
		    set ia {}
		}
		# go over the arguments
		foreach in $ia {
		    if {![dict exists $in Name] ||
			![dict exists $in DataType]} {
			# should not happen
			continue
		    }
		    set name [dict get $in Name]
		    set typeid [dict get $in DataType]
		    lappend plist $name
		    # caution: type name can have a namespace index
		    set type [read $handle $typeid BrowseName]
		    if {[string match "*:*" $type]} {
			set short \
			    [string range $type [string first ":" $type]+1 end]
			if {$short in [types list $handle]} {
................................................................................
	    set doc [dom parse $string]
	}]} {
	    return $out
	}
	set root [$doc documentElement]
	# process namespaces
	array set ns {}
	foreach att [$root attributes] {
	    if {[llength $att] == 3} {
		lassign $att loc pfx uri
	    }
	    if {$uri ne ""} continue
	    set nsx "xmlns:$loc"
	    if {[$root hasAttribute $nsx]} {
		set ns($pfx) [$root getAttribute $nsx]
	    }
	}
................................................................................
		    continue
		}
		if {[$field hasAttribute SwitchField]} {
		    # cannot process this struct
		    set stf {}
		    break
		}

		if {![$field hasAttribute TypeName]} {
		    # cannot process this struct
		    set stf {}
		    break
		}
		set nstype [$field getAttribute TypeName]
		lassign [split $nstype :] fns type
................................................................................
		    lappend stf $nstype
		}
		if {![$field hasAttribute Name]} {
		    # cannot process this struct
		    set stf {}
		    break
		}
		set fn [$field getAttribute Name]
		# handle attribute LengthField, it makes the field
		# into an array, if not empty; the field's name is
		# set to 'field' for scalar, and '*field' for array
		if {[$field hasAttribute LengthField] &&
		    [$field getAttribute LengthField] ne ""} {
		    set fn "*${fn}"
		}
		lappend stf $fn
	    }
	    if {[llength $stf] && [$struct hasAttribute Name]} {
		set st([$struct getAttribute Name]) $stf
	    }
	}
	# structs need further checking later
	set stl [array names st]
................................................................................
	    }
	}
	foreach n $todo {
	    _resolve $n have need later defs
	}
    }

    # Generate ExtensionObject defs for enums and structs.
    # Method information of a server handle must be saved
    # and restored, since the "gentypes" proc recreates
    # type mappings.

    proc gentypes {handle} {
	if {[info $handle] ne "server"} {
	    tailcall _gentypes $handle
	}
	set msaved [methods $handle]
	set ret [catch {_gentypes $handle} err]
	foreach {m t cb} $msaved {
	    # try to restore method output types
	    catch {methods $handle $m $t}
	}
	if {$ret} {
	    return -code $ret $err
	}
    }

    proc _gentypes {handle} {
	foreach {name nodeid encid bsdname} [_getstructs $handle] {
	    set stn($name) $nodeid
	    set enc($name) $encid
	    set bsn($bsdname) $name
	}
	if {![array exists stn]} {
	    return
................................................................................
		set id [lindex $id 0]
		if {[catch {
		    translate $handle $id HasEncoding {Default Binary}
		} eid]} {
		    set eid $id
		}
		set eid [lindex $eid 0]
		if {![catch {types nodeid $handle $en}]} {
		    # already defined, skip
		    continue
		}
		lappend defs [list typedef $handle enum \
				  $en $id $eid $type \
				  {*}[lrange $ed 1 end]]
	    }
	    foreach {sn sd} [dict get $data structs] {
		if {![::info exists bsn($sn)]} {
		    # no mapping from *.bsd to address space
................................................................................
		    # verify namespace index
		    if {[scan $stn($sn) "ns=%d;i=%d" ns id] != 2} {
			continue
		    }
		    if {$ns != $nsindex} {
			continue
		    }
		}
		if {![catch {types nodeid $handle $sn}]} {
		    # already defined, skip
		    continue
		}
		set def [list typedef $handle struct $sn $stn($bn) $enc($bn)]
		foreach {type member} $sd {
		    if {[regsub -- ^tns: $type ${nsindex}: type]} {
			# enums have two elements
			scan [lindex $type 0] "%d:%s" tns ttype
			if {![::info exists have($ttype)]} {
................................................................................
	    # add reference to encoding object
	    add $handle Reference $te HasDescription $tx 1
	}
	append bsd "</opc:TypeDictionary>\n"
	# store *.bsd type defs into node
	write $handle $tt ByteString $bsd
    }

    # Internal helper: get boolean XML attribute.

    proc _ld_getboolatt {node att def} {
	set ret $def
	if {[$node hasAttribute $att]} {
	    set ret [expr {[$node getAttribute $att] ? 1 : 0}]
	}
	return $ret
    }

    # Internal helper: set node reference info from XML dom node.

    proc _ld_noderefs {node dict} {
	upvar $dict d
	set ret {}
	foreach ref [$node selectNodes References/Reference] {
	    dict set r forward [_ld_getboolatt $ref IsForward 1]
	    dict set r type [$ref getAttribute ReferenceType]
	    dict set r nodeid [[$ref firstChild] data]
	    lappend ret $r
	    unset r
	}
	dict set d refs $ret
    }

    # Internal helper: set node's browse/display names etc. from XML dom node.

    proc _ld_nodenames {node dict} {
	upvar $dict d
	set brname [$node getAttribute BrowseName]
	if {[$node hasAttribute DisplayName]} {
	    set dname [$node getAttribute DisplayName]
	} else {
	    set n [string first ":" $brname]
	    if {$n >= 0} {
		set dname [string range $brname $n+1 end]
	    } else {
		set dname $brname
	    }
	}
	dict set d brname $brname
	dict set d dname $dname
	if {[$node hasAttribute ParentNodeId]} {
	    dict set d parent [$node getAttribute ParentNodeId]
	}
    }

    # Internal helper: add parent to node dict if needed,
    # sets the first reverse reference as parent node and
    # adds dict key "refid" with the node id of the reference.

    proc _ld_fixparent {handle dict} {
	upvar $dict d
	if {![dict exists $d parent]} {
	    foreach r [dict get $d refs] {
		if {![dict get $r forward]} {
		    # Use only hierarchical references as parent,
		    # otherwise leave key "parent" empty.
		    if {[dict get $r type] ni {HasModellingRule
			HasTypeDefinition HasEncoding HasDescription
			GeneratesEvent NonHierarchicalReferences}} {
			dict set d parent [dict get $r nodeid]
			break
		    }
		}
	    }
	    if {![dict exists $d parent]} {
		dict set d parent {}
	    }
	}
	if {![dict exists $d refid]} {
	    set parent [dict get $d parent]
	    foreach r [dict get $d refs] {
		if {[dict get $r nodeid] eq $parent &&
		    ![dict get $r forward]} {
		    set type [dict get $r type]
		    if {![catch {reftype $type} rtype]} {
			set type $rtype
		    }
		    dict set d refid $type
		    break
		}
	    }
	    if {![dict exists $d refid]} {
		dict set d refid {}
	    }
	}
	if {![dict exists $d type]} {
	    switch -- [dict get $d kind] {
		Object - VariableType {
		    foreach r [dict get $d refs] {
			if {[dict get $r type] eq "HasTypeDefinition"} {
			    dict set d type [dict get $r nodeid]
			    break
			}
		    }
		}
	    }
	}
    }

    # Internal helper: fix namespace indices in node info dicts.
    #  vn:    name of dict with node info
    #  vm:    array with from->to mapping
    #  nkeys: list of nodeid fields to fix
    #  pkeys: list of qualified names to fix

    proc _ld_fixns {vn vm nkeys pkeys} {
	upvar $vn n
	upvar $vm m
	foreach k $nkeys {
	    if {[dict exists $n $k]} {
		set v [dict get $n $k]
		foreach mm [array names m] {
		    if {[string match "ns=$mm;*" $v]} {
			regsub -- "ns=$mm;" $v "ns=$m($mm);" v
			dict set n $k $v
			break
		    }
		}
	    }
	}
	foreach k $pkeys {
	    if {[dict exists $n $k]} {
		set v [dict get $n $k]
		foreach mm [array names m] {
		    if {[string match "$mm:*" $v]} {
			regsub -- "$mm:" $v "$m($mm):" v
			dict set n $k $v
			break
		    }
		}
	    }
	}
	# special case for values of ExtensionObjects
	if {[dict exists $n value] && [dict exists $n fixnsv]} {
	    set v [dict get $n value]
	    if {[dict get $n fixnsv] < 0} {
		# scalar
		set t [dict get $v DataType]
		foreach mm [array names m] {
		    if {[string match "ns=$mm;*" $t]} {
			regsub -- "ns=$mm;" $t "ns=$m($mm);" t
			dict set v DataType $t
			dict set n value $v
			break
		    }
		}
	    } else {
		# list
		set nv {}
		foreach el $v {
		    set t [dict get $el DataType]
		    foreach mm [array names m] {
			if {[string match "ns=$mm;*" $t]} {
			    regsub -- "ns=$mm;" $t "ns=$m($mm);" t
			    dict set el DataType $t
			    break
			}
		    }
		    lappend nv $el
		}
		dict set n value $nv
	    }
	}
    }

    # Internal helper: recursively convert XML node to given dict variable.

    proc _ld_xn2d {node dict} {
	upvar $dict d
	if {[$node hasChildNodes]} {
	    set val {}
	    foreach child [$node childNodes] {
		if {[$child nodeType] eq "TEXT_NODE"} {
		    dict set d [$node nodeName] [$child nodeValue]
		    return
		}
		_ld_xn2d $child val
	    }
	} elseif {[$node nodeType] eq "TEXT_NODE"} {
	    set val [$node nodeValue]
	} else {
	    set val {}
	}
	dict set d [$node nodeName] $val
    }

    # Internal helper: fix dictionary for missing stuff in
    # Argument and EnumValueType structures.

    proc _ld_x2d_fix {dict} {
	upvar $dict d
	if {[dict exists $d DataType]} {
	    catch {
		dict set d DataType [dict get $d DataType Identifier]
	    }
	}
	foreach key {Description DisplayName} {
	    set nd {}
	    set lcl ""
	    set txt ""
	    catch {set lcl [dict get $d $key Locale]}
	    catch {set txt [dict get $d $key Text]}
	    dict set nd locale $lcl
	    dict set nd text $txt
	    dict set d $key $nd
	}
    }

    # Internal helper: convert XML string to dict whose value is returned.

    proc _ld_x2d {xml} {
	set d {}
	set doc [dom parse -ignorexmlns -- $xml]
	set root [$doc documentElement]
	set item [$root selectNodes {/ExtensionObject/Body[1]}]
	set item [$item firstChild]
	_ld_xn2d $item d
	set d [dict get $d [dict keys $d]]
	_ld_x2d_fix d
	$doc delete
	return $d
    }

    # Internal helper: like _ld_x2d but processing a list of items.

    proc _ld_x2d_list {xml} {
	set dl {}
	set doc [dom parse -ignorexmlns -- $xml]
	set root [$doc documentElement]
	foreach child [$root selectNodes {ExtensionObject/Body[1]}] {
	    set child [$child firstChild]
	    set d {}
	    _ld_xn2d $child d
	    set d [dict get $d [dict keys $d]]
	    _ld_x2d_fix d
	    lappend dl $d
	}
	$doc delete
	return $dl
    }

    # Internal helper: convert value according to type.

    proc _ld_convval {type node data} {
	switch -glob -- $type {
	    *ByteString {
		# try to convert from base64
		if {![catch {binary decode base64 $data} ndata]} {
		    set data $ndata
		}
	    }
	    *LocalizedText {
		if {$node ne {}} {
		    set lcl ""
		    set txt ""
		    foreach n [$node childNodes] {
			switch -glob -- [$n nodeName] {
			    *Locale {
				catch {
				    set lcl [string trim [[$n firstChild] data]]
				}
			    }
			    *Text {
				catch {
				    set txt [string trim [[$n firstChild] data]]
				}
			    }
			}
		    }
		    dict set dd locale $lcl
		    dict set dd text $txt
		    set data $dd
		}
	    }
	}
	return $data
    }

    # Internal helper: write value attribute into node,
    # on error recursively try super types, if any.

    proc _ld_wrvalue {handle nodeid type value} {
	if {[catch {write $handle $nodeid Value $type $value} err]} {
	    foreach {n b d c r t} [browse $handle $type Inverse HasSubtype] {
		if {![catch {_ld_wrvalue $handle $nodeid $n $value}]} {
		    return
		}
	    }
	    return -code error $err
	}
    }

    # Internal helper: create a node given handle and node dict.

    proc _ld_mknode {handle nsindex dict {mvar {}}} {
	upvar $dict d
	dict with d {
	    set att [attrs default ${kind}Attributes]
	    if {[::info exists dname]} {
		dict set att DisplayName locale {}
		dict set att DisplayName text $dname
	    }
	    if {[::info exists descr]} {
		dict set att Description locale {}
		dict set att Description text $descr
	    }
	    switch -- $kind {
		Object {
		    if {![::info exists type]} {
			set type {}
		    }
		    add $handle Object \
			$nodeid $parent $refid $brname $type $att
		}
		Method {
		    # TBD: in/out args
		    set mname ::opcua::${handle}::impl${nsindex}_${dname}
		    add $handle Method $nodeid $parent $refid \
			{} $brname {} $mname $att
		    if {$mvar ne {}} {
			upvar $mvar mnames
			lappend mnames($mname) $nodeid
		    }
		}
		ReferenceType {
		    dict set att IsAbstract $abstract
		    dict set att Symmetric $symmetric
		    add $handle ReferenceType \
			$nodeid $parent $refid $brname $att
		}
		Variable {
		    if {[::info exists type]} {
			dict set att DataType $type
		    }
		    add $handle Variable $nodeid $parent $refid $brname {} $att
		    # TBD: ArrayDimensions
		    write $handle $nodeid ValueRank Int32 $rank
		    # TBD: more complex values
		    if {[::info exists type] && [::info exists value]} {
			_ld_wrvalue $handle $nodeid $type $value
		    }
		}
		ObjectType {
		    dict set att IsAbstract $abstract
		    add $handle $kind $nodeid $parent $refid $brname $att
		}
		DataType {
		    dict set att IsAbstract $abstract
		    add $handle $kind $nodeid $parent $refid $brname $att
		    # TBD: handle XML <Definition>
		}
		VariableType {
		    if {![::info exists type]} {
			set type {}
		    }
		    dict set att IsAbstract $abstract
		    add $handle VariableType \
			$nodeid $parent $refid $brname $type $att
		}
	    }
	}
    }

    # Load nodeset from XML into a opcua server handle.
    # Optional error reports are written to the caller's variables
    # "evar" (for node information) and "rvar" (for references).
    # Function returns a pairwise list of method names and
    # sublist of nodeids for the respective method name.

    proc loader {xml handle {evar {}} {rvar {}}} {
	if {[info $handle] ne "server"} {
	    return -code error "server handle required"
	}
	if {[catch {
	    package require tdom
	    dom parse -ignorexmlns -- $xml
	} doc]} {
	    return -code error "XML parse failed: $doc"
	}
	set root [$doc documentElement]
	# load XML namespaces
	array set ns {}
	set nscount 1
	foreach nsuri [$root selectNodes /UANodeSet/NamespaceUris/Uri/text()] {
	    set nsuri [$nsuri data]
	    set ns($nscount) $nsuri
	    incr nscount
	}
	# find out our namespace
	set mainns 1
	foreach node [$root selectNodes /UANodeSet/Models/Model] {
	    set uri [$node getAttribute ModelUri]
	    foreach nscount [array names ns] {
		if {$ns($nscount) eq $uri} {
		    set mainns $nscount
		    break
		}
	    }
	    break
	}
	if {![::info exists ns($mainns)]} {
	    $doc delete
	    return -code error "main XML namespace not found"
	}

	set mainnsuri $ns($mainns)
	# load aliases
	array set alias {}
	foreach node [$root selectNodes /UANodeSet/Aliases/Alias] {
	    set an [$node getAttribute Alias]
	    set alias($an) [[$node firstChild] data]
	    unset an
	}
	# provide alias for Argument typically used in InputArguments
	# and OutputArguments
	if {![::info exists alias(Argument)]} {
	    set alias(Argument) [types nodeid Argument]
	}
	# load node information from XML
	array set nodes {}
	foreach {kinds code} {
	    {Object Method} {
	    }
	    ReferenceType {
		dict set n symmetric [_ld_getboolatt $node Symmetric 0]
		dict set n abstract [_ld_getboolatt $node IsAbstract 0]
	    }
	    Variable {
		set vr -2
		if {[$node hasAttribute ValueRank]} {
		    set vr [$node getAttribute ValueRank]
		    set dim 0
		    if {$vr >= 0 && [$node hasAttribute ArrayDimensions]} {
			set dim [$node getAttribute ArrayDimensions]
		    }
		    dict set n dim $dim
		}
		dict set n rank $vr
		if {[$node hasAttribute DataType]} {
		    set type [$node getAttribute DataType]
		    set uty $type
		    if {[::info exists alias($type)]} {
			set uty $alias($type)
		    }
		    dict set n type $uty
		    if {![catch {$node selectNodes Value} v] &&
			![catch {$v firstChild} v]} {
			switch -glob -- [$v nodeName] {
			    *ListOfExtensionObject {
				set xml [$v asXML]
				# Mangle XML to have no namespace prefixes.
				set nn [split [$v nodeName] :]
				set nnn ""
				lassign $nn nns $nnn
				if {$nnn eq ""} {
				    regsub -all "<$nns:" $xml "<" xml
				    regsub -all "</$nns:" $xml "</" xml
				}
				set vd {}
				set nnn 0
				foreach el [_ld_x2d_list $xml] {
				    lappend vd $el
				    if {[dict exists $el DataType]} {
					incr nnn
				    }
				}
				dict set n value $vd
				if {$nnn > 0} {
				    dict set n fixnsv $nnn
				}
			    }
			    *ExtensionObject {
				set xml [$v asXML]
				# Mangle XML to have no namespace prefixes.
				set nn [split [$v nodeName] :]
				set nnn ""
				lassign $nn nns $nnn
				if {$nnn eq ""} {
				    regsub -all "<$nns:" $xml "<" xml
				    regsub -all "</$nns:" $xml "</" xml
				}
				set vd [_ld_x2d $xml]
				if {$vr >= 0} {
				    dict set n value [list $vd]
				    if {[dict exists $vd DataType]} {
					set set n fixnsv 1
				    }
				} else {
				    dict set n value $vd
				    if {[dict exists $vd DataType]} {
					set set n fixnsv -1
				    }
				}
			    }
			    *ListOf* {
				set lv {}
				foreach vv [$v childNodes] {
				    set vtype [$vv nodeName]
				    if {![catch {$vv firstChild} vvv]} {
					if {[catch {$vvv data} data]} {
					    set data {}
					}
					lappend lv \
					    [_ld_convval $vtype $vv $data]
				    } else {
					lappend lv {}
				    }
				}
				dict set n value $lv
			    }
			    default {
				if {![catch {$v firstChild} vv]} {
				    if {[catch {$vv data} data]} {
					set data {}
				    }
				    set vvv [_ld_convval $type $vv $data]
				    if {$vr >= 0} {
					dict set n value [list $vvv]
				    } else {
					dict set n value $vvv
				    }
				}
			    }
			}
		    }
		}
	    }
	    {ObjectType VariableType DataType} {
		dict set n abstract [_ld_getboolatt $node IsAbstract 0]
	    }
	} {
	    foreach kind $kinds {
		foreach node [$root selectNodes /UANodeSet/UA$kind] {
		    dict set n kind $kind
		    set ni [$node getAttribute NodeId]
		    dict set n nodeid $ni
		    if {[$node hasAttribute Description]} {
			dict set n descr [$node getAttribute Description]
		    }
		    _ld_noderefs $node n
		    _ld_nodenames $node n
		    eval $code
		    _ld_fixparent $handle n
		    set nodes($ni) $n
		    unset n ni
		}
	    }
	}
	# done with XML
	$doc delete

	# done with aliases
	unset alias

	# create and find out namespaces
	array set sns {}
	foreach {nscount nsuri} [namespace $handle] {
	    set sns($nsuri) $nscount
	}
	if {[::info exists sns($mainnsuri)]} {
	    return -code error "namespace $mainnsuri exists"
	}
	foreach nscount [array names ns] {
	    set nn $ns($nscount)
	    if {$nscount != $mainns && ![::info exists sns($nn)]} {
		return -code error "namespace $nn is not defined"
	    }
	    unset nn
	}
	set mainns [add S Namespace $mainnsuri]

	# fix namespace indices in nodes array
	foreach {nscount nsuri} [namespace $handle] {
	    set sns($nsuri) $nscount
	}
	array set nsmap {}
	foreach nscount [array names ns] {
	    set nn $ns($nscount)
	    if {$sns($nn) != $nscount} {
		set nsmap($nscount) $sns($nn)
	    }
	    unset nn
	}

	foreach nodeid [array names nodes] {
	    _ld_fixns nodes($nodeid) nsmap {nodeid parent refid type} brname
	    set refs [dict get $nodes($nodeid) refs]
	    set nrefs {}
	    foreach ref $refs {
		_ld_fixns ref nsmap {type nodeid} {}
		lappend nrefs $ref
	    }
	    dict set nodes($nodeid) refs $nrefs
	    unset refs nrefs
	}

	# fix array names of nodes
	array set nnodes {}
	foreach nodeid [array names nodes] {
	    set node $nodes($nodeid)
	    unset nodes($nodeid)
	    set nodeid [dict get $node nodeid]
	    set nnodes($nodeid) $node
	    unset node
	}
	array set nodes [array get nnodes]
	unset nnodes

	# prime cache of existing nodeids
	array set exists {}
	foreach type [reftype] {
	    set exists([reftype $type]) 1
	}
	foreach type [types basic] {
	    set exists([types nodeid $type]) 1
	}

	# create nodes
	set max [array size nodes]
	set count 0
	array set refs {}
	array set errnodes {}
	array set meths {}
	while {$count < $max} {
	    foreach nodeid [array names nodes] {
		set node $nodes($nodeid)
		set parent [dict get $node parent]
		set nopaskip 1
		if {$parent ne {} && ![::info exists exists($parent)]} {
		    # Query server for parent nodeid.
		    if {![catch {read $handle $parent NodeClass}]} {
			set exists($parent) 1
		    } else {
			set nopaskip 0
		    }
		}
		set noreskip 1
		if {[dict exists $node refid]} {
		    set refid [dict get $node refid]
		    if {$refid ne {} && ![::info exists exists($refid)]} {
			# Query server for reference nodeid.
			if {![catch {read $handle $refid NodeClass}]} {
			    set exists($refid) 1
			} else {
			    set noreskip 0
			}
		    }
		}
		set notyskip 1
		if {[dict exists $node type]} {
		    set typeid [dict get $node type]
		    if {![::info exists exists($typeid)]} {
			# Query server for type nodeid.
			if {![catch {read $handle $typeid NodeClass}]} {
			    set exists($typeid) 1
			} else {
			    set notyskip 0
			}
		    }
		}
		if {$nopaskip && $noreskip && $notyskip} {
		    # Create node.
		    if {[catch {
			_ld_mknode $handle $mainns nodes($nodeid) meths
		    } err]} {
			set errnodes($nodeid) $nodes($nodeid)
			dict set errnodes($nodeid) error $err
			# Check and record node's existence.
			set nex [catch {read $handle $nodeid NodeClass}]
			dict set errnodes($nodeid) exists [expr {!$nex}]
			if {!$nex} {
			    set exists($nodeid) 1
			}
		    } else {
			set exists($nodeid) 1
		    }
		    unset nodes($nodeid)
		    # Create list of references to be made later.
		    set nrefs {}
		    foreach ref [dict get $node refs] {
			if {[dict get $ref nodeid] eq $parent} {
			    continue
			}
			lappend nrefs $ref
		    }
		    if {[llength $nrefs]} {
			set refs($nodeid) $nrefs
		    }
		}
	    }
	    incr count
	}

	foreach nodeid [array names nodes] {
	    set errnodes($nodeid) $nodes($nodeid)
	    dict set errnodes($nodeid) error {some required node does not exist}
	    unset nodes($nodeid)
	}

	# create references
	array set unrefs {}
	foreach nodeid [array names refs] {
	    foreach ref $refs($nodeid) {
		set refid [dict get $ref nodeid]
		if {![::info exists exists($refid)]} {
		    # Query server for reference nodeid.
		    if {![catch {read $handle $refid NodeClass}]} {
			set exists($refid) 1
		    }
		}
		if {[::info exists exists($refid)] &&
		    ![::info exists nodes($refid)] &&
		    [::info exists exists($nodeid)]} {
		    # Create reference.
		    if {[catch {add $handle Reference $nodeid \
				    [dict get $ref type] \
				    $refid [dict get $ref forward]} err]} {
			if {![string match "*already defined" $err]} {
			    return -code error $err
			}
		    }
		} else {
		    set unrefs($refid) $ref
		}
	    }
	    unset refs($nodeid)
	}

	# TBD: retry failed "opcua write" operations with
	# generated types, however better fix would be to
	# process XML <Definition> in DataType XML nodes.
	gentypes $handle
	foreach nodeid [array names errnodes] {
	    if {[::info exists exists($nodeid)]} {
		set node $errnodes($nodeid)
		if {[dict exists $node type] && [dict exists $node value]} {
		    if {![catch {
			_ld_wrvalue $handle $nodeid [dict get $node type] \
			    [dict get $node value]
		    } err]} {
			unset errnodes($nodeid)
		    }
		}
	    }
	}

	# leave error reports for caller, if requested
	if {$evar ne {}} {
	    upvar $evar ev
	    set ev [array get errnodes]
	}
	if {$rvar ne {}} {
	    upvar $rvar rv
	    set rv [array get unrefs]
	}

	# cleanup namespace folder, it seems that open62541 makes
	# own dynamic nodes on "opcua add Namespace".
	catch {
	    set nsf [translate $handle [root] / Objects / Server / \
			 Namespaces / ${mainns}:${mainnsuri}]
	    foreach nodeid [children $handle [lindex $nsf 0]] {
		if {![::info exists exists($nodeid)]} {
		    delete $handle Node $nodeid 1
		}
	    }
	}

	# try to add method output types to newly created methods
	foreach mn [array names meths] {
	    foreach nodeid $meths($mn) {
		catch {
		    set oa [translate $handle $nodeid \
				HasProperty OutputArguments]
		    set oid [lindex $oa 0]
		    # currently only the first element can be handled
		    set rfield [lindex [read $handle $oid] 0]
		    methods $handle $nodeid [dict get $rfield DataType]
		}
	    }
	}

	# return method names and nodeids
	return [array get meths]
    }

    # Make some procs visible in opcua ensemble.

    apply [list ns {
	set cmds [::namespace ensemble configure $ns -subcommands]
	lappend cmds tree ptree children parent genstubs gentypes deftypes
	lappend cmds loader
	::namespace ensemble configure $ns -subcommands $cmds
    } [::namespace current]] [::namespace current]

}

Changes to jni/topcua/topcua.c.

65
66
67
68
69
70
71
72
73
74
75
76
77
78

79
80
81
82
83
84
85
...
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
...
961
962
963
964
965
966
967
968
969
970
971
972
973





974
975
976
977
978
979
980
...
993
994
995
996
997
998
999



1000
1001
1002
1003
1004
1005





1006
1007
1008
1009



1010
1011
1012
1013
1014
1015
1016
....
1032
1033
1034
1035
1036
1037
1038















1039
1040
1041
1042
1043
1044
1045
....
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
....
2504
2505
2506
2507
2508
2509
2510
2511

2512
2513
2514

2515
2516
2517
2518
2519
2520
2521
2522

2523
2524
2525
2526
2527
2528
2529
....
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601

2602
2603
2604
2605
2606
2607
2608
2609

2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
....
2871
2872
2873
2874
2875
2876
2877
2878

2879
2880
2881
2882
2883
2884
2885
2886

2887
2888
2889
2890
2891

2892



2893


















2894
2895
2896



2897
2898
2899
2900
2901
2902
2903
....
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970

2971
2972
2973
2974
2975
2976
2977
2978

2979
2980

2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007

3008
3009
3010
3011
3012
3013
3014
....
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
....
3241
3242
3243
3244
3245
3246
3247
3248

3249
3250
3251
3252
3253

3254
3255
3256
3257
3258
3259
3260
3261

















3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278




3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289

3290
3291
3292
3293
3294
3295
3296
....
3345
3346
3347
3348
3349
3350
3351

3352
3353
3354








3355
3356
3357
3358
3359
3360
3361
....
3414
3415
3416
3417
3418
3419
3420

3421
3422
3423
3424
3425
3426
3427
....
3478
3479
3480
3481
3482
3483
3484


3485
3486
3487








3488
3489
3490
3491
3492
3493
3494
....
3582
3583
3584
3585
3586
3587
3588

3589
3590
3591
3592
3593
3594
3595
....
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
....
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
....
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
....
5541
5542
5543
5544
5545
5546
5547
5548

5549
5550
5551
5552
5553
5554
5555
....
5562
5563
5564
5565
5566
5567
5568

5569
5570
5571
5572
5573
5574
5575
....
5576
5577
5578
5579
5580
5581
5582
5583















5584













































5585
5586
5587
5588
5589
5590

5591
5592
5593
5594
5595
5596
5597
5598
5599
5600



5601

5602
5603
5604
5605
5606
5607
5608
....
5911
5912
5913
5914
5915
5916
5917
5918
5919
5920
5921
5922
5923
5924
5925
5926
5927
5928
5929
5930
5931
5932
5933
....
6242
6243
6244
6245
6246
6247
6248
6249
6250
6251
6252
6253
6254
6255
6256
6257
6258
6259
6260
6261
6262
....
6709
6710
6711
6712
6713
6714
6715

6716
6717
6718
6719
6720
6721
6722

6723
6724
6725
6726
6727
6728
6729
....
6837
6838
6839
6840
6841
6842
6843
6844
6845
6846
6847
6848
6849
6850
6851
....
6859
6860
6861
6862
6863
6864
6865

6866
6867
6868
6869
6870
6871
6872
6873
6874
6875
6876
6877
6878
6879
6880
6881
6882
6883
6884





6885
6886
6887












6888
6889
6890
6891
6892
6893
6894
....
7082
7083
7084
7085
7086
7087
7088
7089

7090
7091
7092
7093
7094
7095
7096
....
7115
7116
7117
7118
7119
7120
7121
7122
7123
7124
7125
7126
7127
7128
7129
7130
7131
7132
7133
7134
7135
7136
....
7147
7148
7149
7150
7151
7152
7153
7154
7155
7156
7157
7158
7159
7160
7161
....
7186
7187
7188
7189
7190
7191
7192





7193
7194
7195
7196
7197
7198
7199
7200
7201
7202
7203
7204
7205
7206
7207
7208





7209
7210
7211












7212
7213
7214
7215
7216
7217
7218
....
7342
7343
7344
7345
7346
7347
7348
7349


7350
7351
7352
7353
7354
7355
7356
....
7363
7364
7365
7366
7367
7368
7369









































7370
7371
7372
7373
7374
7375
7376
....
7390
7391
7392
7393
7394
7395
7396

7397
7398
7399

7400
7401

7402
7403
7404
7405
7406
7407
7408
7409
7410
7411
7412

7413



7414
7415
7416
7417
7418



7419
7420

7421



7422
7423
7424
7425
7426
7427
















































7428
7429
7430
7431
7432
7433
7434
....
7777
7778
7779
7780
7781
7782
7783
7784
7785
7786
7787
7788
7789
7790
7791
....
7893
7894
7895
7896
7897
7898
7899






7900
7901
7902
7903
7904
7905
7906
7907
7908
7909
7910
7911
7912
....
7940
7941
7942
7943
7944
7945
7946
7947

7948





7949
7950
7951
7952
7953
7954
7955
7956
....
7959
7960
7961
7962
7963
7964
7965
7966
7967
7968
7969
7970
7971
7972
7973
....
8063
8064
8065
8066
8067
8068
8069
8070
8071
8072
8073
8074
8075
8076
8077


8078
8079
8080
8081
8082
8083
8084
8085
8086
8087
8088
8089


8090
8091

8092
8093
8094
8095
8096
8097
8098
8099
8100
8101
8102
8103

8104
8105
8106
8107
8108
8109
8110
....
8165
8166
8167
8168
8169
8170
8171
8172
8173
8174

8175
8176
8177
8178
8179

8180
8181
8182
8183
8184
8185
8186
8187
8188
8189
8190
8191
8192
8193
} UAM;

/*
 * Structure representing a method invocation. This is used for
 * both method callbacks and data source callbacks.
 */

typedef struct {
    struct UAH *uah;			/* Containing server handle. */
    const UA_DataType *outType;		/* Output data type or NULL. */
    int nCmdObjs;			/* For the Tcl callback. */
    Tcl_Obj **cmdObjs;			/* Ditto. */
    int nSavedCmdObjs;			/* Ditto. */
    Tcl_Obj **savedCmdObjs;		/* Ditto. */

} UAQ;

/*
 * Structure representing an OPC/UA client or server.
 */

typedef struct UAH {
................................................................................
					  UA_ReferenceDescription *ref);
static Tcl_Obj *	DecodeToObj(UAI *uai, const UA_DataType *type,
				    const void *data, int depth);
static void *		EncodeFromObj(Tcl_Interp *interp, UAI *uai, UAH *uah,
				      const UA_DataType *type,
				      const void *preset, Tcl_Obj *obj,
				      int depth);
static UA_Argument *	GetMethodArgs(Tcl_Interp *interp, UAH *uah,
				      Tcl_Obj *obj, int *numRet);
static UA_StatusCode	MethodInvoke(UA_Server *server,
				     const UA_NodeId *sessionId,
				     void *sessionContext,
				     const UA_NodeId *methodId,
				     void *methodContext,
				     const UA_NodeId *objectId,
................................................................................
 */

static void
ReleaseMethodsEtc(UAH *uah, int killProcs)
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    UAQ *meth, *dsrc;
    int i;

    hPtr = Tcl_FirstHashEntry(&uah->meths, &search);
    while (hPtr != NULL) {
	meth = (UAQ *) Tcl_GetHashValue(hPtr);





	Tcl_DeleteHashEntry(hPtr);
	if (killProcs) {
	    Tcl_Obj *objs[4];

	    /*
	     * Evaluates to "::namespace eval NS { ::rename P {} }"
	     * ignoring errors.
................................................................................
	    for (i = 0; i < 4; i++) {
		Tcl_DecrRefCount(objs[i]);
	    }
	}
	for (i = 0; i < meth->nCmdObjs; i++) {
	    Tcl_DecrRefCount(meth->cmdObjs[i]);
	}



	ckfree((char *) meth);
	hPtr = Tcl_NextHashEntry(&search);
    }
    hPtr = Tcl_FirstHashEntry(&uah->dsrcs, &search);
    while (hPtr != NULL) {
	dsrc = (UAQ *) Tcl_GetHashValue(hPtr);





	Tcl_DeleteHashEntry(hPtr);
	for (i = 0; i < dsrc->nCmdObjs; i++) {
	    Tcl_DecrRefCount(dsrc->cmdObjs[i]);
	}



	ckfree((char *) dsrc);
	hPtr = Tcl_NextHashEntry(&search);
    }
}
 
/*
 *-------------------------------------------------------------------------
................................................................................
ReleaseTypes(UAH *uah, int all)
{
    UAI *uai = uah->uai;
    size_t i, j;
    Tcl_Obj *obj;

    if (uah->types != NULL) {















	for (i = 0; i < uah->typesSize; i++) {
	    for (j = 0; j < uah->types[i].membersSize; j++) {
		if (uah->types[i].members[j].memberName != NULL) {
		    obj = GetSymObj(uai, uah->types[i].members[j].memberName,
				    NULL);
		    Tcl_DecrRefCount(obj);
		}
................................................................................
 */

static Tcl_Obj *
GetSymObj(UAI *uai, const char *string, char **keyRet)
{
    Tcl_Obj *obj;
    Tcl_HashEntry *hPtr;
    int new;

    hPtr = Tcl_CreateHashEntry(&uai->syms, string, &new);
    if (new) {
	obj = Tcl_NewStringObj(string, -1);
	Tcl_IncrRefCount(obj);
	Tcl_SetHashValue(hPtr, (ClientData) obj);
    } else {
	obj = (Tcl_Obj *) Tcl_GetHashValue(hPtr);
    }
    if (keyRet != NULL) {
................................................................................
    }
    if (type == &UA_TYPES[UA_TYPES_UINT64]) {
	return Tcl_NewWideIntObj(((Tcl_WideInt *) data)[0]);
    }
    if (type == &UA_TYPES[UA_TYPES_FLOAT]) {
	return Tcl_NewDoubleObj(((float *) data)[0]);
    }
    if (type == &UA_TYPES[UA_TYPES_DOUBLE]) {

	return Tcl_NewDoubleObj(((double *) data)[0]);
    }
    if (type == &UA_TYPES[UA_TYPES_STRING]) {

	UA_String *v = (UA_String *) data;

	if (v->data != NULL) {
	    return Tcl_NewStringObj((char *) v->data, v->length);
	}
	return Tcl_NewObj();
    }
    if (type == &UA_TYPES[UA_TYPES_DATETIME]) {

	UA_DateTime t = ((UA_DateTime *) data)[0];

	return Tcl_NewWideIntObj((Tcl_WideInt) t);
    }
    if (type == &UA_TYPES[UA_TYPES_GUID]) {
	UA_Guid *v = (UA_Guid *) data;
	char buffer[64];
................................................................................

	obj = Tcl_NewStringObj(PrintQualifiedName(v, &ds), -1);
	Tcl_DStringFree(&ds);
	return obj;
    }
    if (type == &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]) {
	UA_LocalizedText *v = (UA_LocalizedText *) data;
	Tcl_Obj *list[2];

	if (v->locale.data != NULL) {
	    list[0] = Tcl_NewStringObj((char * ) v->locale.data,
				       v->locale.length);
	} else {
	    list[0] = Tcl_NewObj();
	}

	if (v->text.data != NULL) {
	    list[1] = Tcl_NewStringObj((char * ) v->text.data,
				       v->text.length);
	} else {
	    list[1] = Tcl_NewObj();
	}
	return Tcl_NewListObj(2, list);
    }

    if (type == &UA_TYPES[UA_TYPES_ARGUMENT]) {
	UA_Argument *v = (UA_Argument *) data;
	Tcl_DString ds;
	Tcl_Obj *list[2];

	/*
	 * Only print nodeid of type and name, leave out array
	 * dimensions and description.
	 */
	list[0] = Tcl_NewStringObj(PrintNodeId(&v->dataType, &ds), -1);
	Tcl_DStringFree(&ds);
	if (v->name.data) {
	    list[1] = Tcl_NewStringObj((char *) v->name.data, v->name.length);
	} else {
	    list[1] = Tcl_NewObj();
	}
	return Tcl_NewListObj(2, list);
    }
    if (type == &UA_TYPES[UA_TYPES_DATAVALUE]) {
	UA_DataValue *v = (UA_DataValue *) data;
	Tcl_Obj *dict = Tcl_NewDictObj();

	if (v->hasValue || empty) {
	    Tcl_Obj *vv;
................................................................................
	double v;

	if (Tcl_GetDoubleFromObj(interp, obj, &v) != TCL_OK) {
	    goto error;
	}
	value = UA_new(type);
	((UA_Float *) value)[0] = v;
    } else if (type == &UA_TYPES[UA_TYPES_DOUBLE]) {

	double v;

	if (Tcl_GetDoubleFromObj(interp, obj, &v) != TCL_OK) {
	    goto error;
	}
	value = UA_new(type);
	((UA_Double *) value)[0] = v;
    } else if (type == &UA_TYPES[UA_TYPES_STRING]) {

	UA_String s = UA_String_fromChars(Tcl_GetString(obj));

	value = UA_new(type);
	UA_String_copy(&s, (UA_String *) value);
    } else if (type == &UA_TYPES[UA_TYPES_DATETIME]) {

	Tcl_WideInt v;






















	if (Tcl_GetWideIntFromObj(interp, obj, &v) != TCL_OK) {
	    goto error;
	}



	value = UA_new(type);
	((UA_DateTime *) value)[0] = (UA_DateTime) v;
    } else if (type == &UA_TYPES[UA_TYPES_GUID]) {
	UA_Guid *g;
	int d[11];

	if (sscanf(Tcl_GetString(obj),
................................................................................
	if (ParseQualifiedName(interp, (UA_QualifiedName *) value,
			       Tcl_GetString(obj)) == NULL) {
	    UA_delete(value, type);
	    value = NULL;
	    goto error;
	}
    } else if (type == &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]) {
	int n;
	Tcl_Obj **elem;

	if (Tcl_ListObjGetElements(interp, obj, &n, &elem) != TCL_OK) {
	    goto error;
	}

	if ((n != 0) && (n != 2)) {
	    Tcl_SetResult(interp, "zero or two elements required", TCL_STATIC);
	    goto error;
	}
	value = UA_new(type);
	if (n > 0) {
	    UA_String s;


	    s = UA_String_fromChars(Tcl_GetString(elem[0]));
	    UA_String_copy(&s, &((UA_LocalizedText *) value)->locale);

	    s = UA_String_fromChars(Tcl_GetString(elem[1]));
	    UA_String_copy(&s, &((UA_LocalizedText *) value)->text);
	}
    } else if (type == &UA_TYPES[UA_TYPES_VARIANT]) {
	int n, j;
	Tcl_Obj **elem;
	const UA_DataType *vtype = NULL;
	void *vdata = NULL;

	if (Tcl_ListObjGetElements(interp, obj, &n, &elem) != TCL_OK) {
	    goto error;
	}
	if (n == 1) {
	    Tcl_SetResult(interp, "zero, two, or more elements required",
			  TCL_STATIC);
	    goto error;
	}
	if (n) {
	    vtype = FindType(uai, uah, 0, Tcl_GetString(elem[0]));
	    if ((vtype == NULL) || (vtype == &UA_TYPES[UA_TYPES_VARIANT])) {
		Tcl_SetResult(interp, "unsupported type", TCL_STATIC);
		goto error;
	    }
	    /* Now skip type element. */
	    elem += 1;
	    n -= 1;
	    if (n == 1) {

		vdata = EncodeFromObj(interp, uai, uah, vtype,
				      NULL, elem[0], depth + 1);
	    } else {
		vdata = UA_Array_new(n, vtype);
		if (vdata == NULL) {
		    Tcl_SetResult(interp, "out of memory", TCL_STATIC);
		    goto error;
................................................................................
	if (vtype != NULL) {
	    if (n == 1) {
		UA_Variant_setScalar(value, vdata, vtype);
	    } else if (n > 1) {
		UA_Variant_setArray(value, vdata, n, vtype);
	    }
	}
    } else if (type == &UA_TYPES[UA_TYPES_ARGUMENT]) {
	int n;
	Tcl_Obj **elem;
	Tcl_HashEntry *hPtr;
	char *p;
	UA_String s;
	UA_Argument *a;

	if (Tcl_ListObjGetElements(interp, obj, &n, &elem) != TCL_OK) {
	    goto error;
	}
	if (n != 2) {
	    Tcl_SetResult(interp, "two elements required", TCL_STATIC);
	    goto error;
	}
	value = UA_new(type);
	a = (UA_Argument *) value;
	a->valueRank = -2;		/* scalar or array */

	/*
	 * Abbrevation: allow names from types hash table.
	 */
	p = Tcl_GetString(elem[0]);
	hPtr = Tcl_FindHashEntry(&uai->types, p);
	if (hPtr != NULL) {
	    UA_DataType *type = (UA_DataType *) Tcl_GetHashValue(hPtr);

	    UA_NodeId_copy(&type->typeId, &a->dataType);
	} else if (ParseNodeId(interp, &a->dataType, p) == NULL) {
	    UA_delete(value, type);
	    value = NULL;
	    goto error;
	}
	s = UA_String_fromChars(Tcl_GetString(elem[1]));
	UA_String_copy(&s, &a->name);
    } else if (type->membersSize > 0) {
	int i;
	void *data;

	value = UA_new(type);
	if (preset != NULL) {
	    UA_copy(preset, value, type);
................................................................................
 * Side effects:
 *	Allocates memory for UA_Argument array.
 *
 *-------------------------------------------------------------------------
 */

static UA_Argument *
GetMethodArgs(Tcl_Interp *interp, UAH *uah, Tcl_Obj *obj, int *numRet)

{
    UAI *uai = uah->uai;
    int i, n;
    Tcl_Obj **elem;
    UA_Argument *a;


    *numRet = -1;	/* Preset to error. */
    if (Tcl_ListObjGetElements(interp, obj, &n, &elem) != TCL_OK) {
	return NULL;
    }
    if (n == 0) {
	*numRet = 0;	/* Good case. */
	return NULL;

















    }
    if (n % 2 != 0) {
	Tcl_SetResult(interp, "need even number of elements", TCL_STATIC);
	return NULL;
    }
    a = UA_Array_new(n / 2, &UA_TYPES[UA_TYPES_ARGUMENT]);
    for (i = 0; i < n; i += 2) {
	UA_Argument *v;
	Tcl_HashEntry *hPtr;
	char *p;
	UA_String s;

	v = &a[i / 2];
	v->valueRank = -2;	/* Allow scalar or array. */

	/* Abbrevation: allow names from types hash table. */
	p = Tcl_GetString(elem[0]);




	hPtr = Tcl_FindHashEntry(&uai->types, p);
	if (hPtr != NULL) {
	    UA_DataType *type = (UA_DataType *) Tcl_GetHashValue(hPtr);

	    UA_NodeId_copy(&type->typeId, &v->dataType);
	} else if (ParseTypeOrNodeId(interp, uah, &v->dataType, p) == NULL) {
	    UA_Array_delete(a, n / 2, &UA_TYPES[UA_TYPES_ARGUMENT]);
	    return NULL;
	}
	s = UA_String_fromChars(Tcl_GetString(elem[1]));
	UA_String_copy(&s, &v->name);

    }
    *numRet = n / 2;
    return a;
}
 
/*
 *-------------------------------------------------------------------------
................................................................................
    Tcl_DStringFree(&ds);
    for (i = 0; i < inputSize; i++) {
	const UA_Variant *v = &input[i];

	Tcl_ListObjAppendElement(NULL, list,
	    DecodeToObj(uai, &UA_TYPES[UA_TYPES_VARIANT], v, 0));
    }

    Tcl_IncrRefCount(list);
    ret = Tcl_GlobalEvalObj(interp, list);
    Tcl_DecrRefCount(list);








    /*
     * Special cases:
     *   TCL_BREAK makes into an array result.
     *   When there's no output type, the result is void.
     */
    if ((meth->outType == NULL) && ((ret == TCL_BREAK) || (ret == TCL_OK))) {
	/* Void result. */
................................................................................
	    Tcl_DecrRefCount(cmdObjs[i]);
	}
	ckfree((char *) cmdObjs);
    } else {
	meth->nCmdObjs = nCmdObjs;
	meth->cmdObjs = cmdObjs;
    }

    Tcl_RestoreInterpState(interp, state);
    Tcl_Release(interp);
    Tcl_Release(uah);
    Tcl_Release(uai);
    return uaret;
}
 
................................................................................
    dsrc->nCmdObjs = 0;
    dsrc->cmdObjs = NULL;
    list = Tcl_NewListObj(nCmdObjs, cmdObjs);
    p = PrintNodeId(nodeId, &ds);
    Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(p, -1));
    Tcl_DStringFree(&ds);
    Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj("read", -1));


    Tcl_IncrRefCount(list);
    ret = Tcl_GlobalEvalObj(interp, list);
    Tcl_DecrRefCount(list);








    if ((ret == TCL_BREAK) || (ret == TCL_OK)) {
	if (Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp),
				   &n, &elem) != TCL_OK) {
	    ret = TCL_ERROR;
	    goto error;
	}
	/*
................................................................................
	    Tcl_DecrRefCount(cmdObjs[i]);
	}
	ckfree((char *) cmdObjs);
    } else {
	dsrc->nCmdObjs = nCmdObjs;
	dsrc->cmdObjs = cmdObjs;
    }

    Tcl_RestoreInterpState(interp, state);
    Tcl_Release(interp);
    Tcl_Release(uah);
    Tcl_Release(uai);
    return uaret;
}

................................................................................
	  int objc, Tcl_Obj * const objv[])
{
    UAI *uai = (UAI *) clientData;
    UAH *uah;
    UA_Client *cl = NULL;
    UA_Server *sv = NULL;
    Tcl_HashEntry *hPtr = NULL;
    int new, isServer = 0, port;
    char *pname, name[16];
    Tcl_DString ds;
    UA_ServerConfig *svcfg = NULL;
    static const char *typeNames[] = { "client", "server", NULL };

    if ((objc != 1) && (objc != 3) && (objc > 4)) {
	Tcl_WrongNumArgs(interp, 1, objv, "?type ?port? name?");
................................................................................
		Tcl_WrongNumArgs(interp, 1, objv, "server port name");
		return TCL_ERROR;
	    }
	    if (Tcl_GetIntFromObj(interp, objv[2], &port) != TCL_OK) {
		return TCL_ERROR;
	    }
	    hPtr = Tcl_CreateHashEntry(&uai->handles,
				       Tcl_GetString(objv[3]), &new);
	} else if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 1, objv, "client name");
	    return TCL_ERROR;
	} else {
	    hPtr = Tcl_CreateHashEntry(&uai->handles,
				       Tcl_GetString(objv[2]), &new);
	}
	if (!new) {
	    Tcl_SetResult(interp, "name already in use", TCL_STATIC);
	    ReportError(interp, uai, NULL, 0);
	    return TCL_ERROR;
	}
    }
    if (isServer) {
#if (UA_OPEN62541_VER_MAJOR >= 1)
................................................................................
    memset(&uah->typesArray, 0, sizeof(uah->typesArray));
#endif
    Tcl_InitHashTable(&uah->subs, TCL_ONE_WORD_KEYS);
    Tcl_InitHashTable(&uah->meths, TCL_STRING_KEYS);
    Tcl_InitHashTable(&uah->dsrcs, TCL_STRING_KEYS);
    if (hPtr == NULL) {
	sprintf(name, "ua%d", uai->idCount++);
	hPtr = Tcl_CreateHashEntry(&uai->handles, name, &new);
	if (!new) {
	    UAH *oldh = (UAH *) Tcl_GetHashValue(hPtr);

	    ReleaseTypes(oldh, 1);
	    ReleaseMethodsEtc(oldh, 1);
	    if (oldh->client != NULL) {
		DisconnectClient(oldh);
		if (oldh->client != NULL) {
................................................................................
    UAH *uah;
    Tcl_HashEntry *hPtr;
    UA_StatusCode uaret;
    UA_NodeId nodeid;
    const UA_DataType *type;
    UA_WriteValue wv;
    void *value;
    int attr, idx;


    if ((objc < 5) || (objc > 6)) {
	Tcl_WrongNumArgs(interp, 1, objv,
			 "handle nodeid ?attr? type value");
	return TCL_ERROR;
    }
    hPtr = Tcl_FindHashEntry(&uai->handles, Tcl_GetString(objv[1]));
................................................................................
    }
    if (ParseNodeId(interp, &nodeid, Tcl_GetString(objv[2])) == NULL) {
	return TCL_ERROR;
    }
    if (objc > 5) {
	if (Tcl_GetIndexFromObj(interp, objv[3], attrNames, "attr", 0,
				&attr) != TCL_OK) {

	    return TCL_ERROR;
	}
	/* UA_AttributeId is 1-based */
	attr += 1;
	idx = 4;
    } else {
	/* By default, write the "value" attribute. */
................................................................................
	attr = UA_ATTRIBUTEID_VALUE;
	idx = 3;
    }
    type = FindType(uai, uah, 0, Tcl_GetString(objv[idx]));
    if (type == NULL) {
	Tcl_SetResult(interp, "unsupported type", TCL_STATIC);
	goto error;
    }















    idx++;













































    value = EncodeFromObj(interp, uai, uah, type, NULL, objv[idx], 0);
    if (value == NULL) {
error:
	ReportError(interp, uai, NULL, 0);
	UA_NodeId_deleteMembers(&nodeid);
	return TCL_ERROR;

    }
    if (uah->client != NULL) {
	uaret = __UA_Client_writeAttribute(uah->client, &nodeid,
					   (UA_AttributeId) attr,
					   value, type);
    } else {
	UA_WriteValue_init(&wv);
	wv.nodeId = nodeid;
	wv.attributeId = (UA_AttributeId) attr;
	wv.value.hasValue = 1;



	UA_Variant_setScalar(&wv.value.value, value, type);

	uaret = UA_Server_write(uah->server, &wv);
    }
    if (uaret != UA_STATUSCODE_GOOD) {
	ReportError(interp, uai, "WriteAttribute", uaret);
    }
    if (uah->client != NULL) {
	UA_NodeId_deleteMembers(&nodeid);
................................................................................
	    sub->prio = prio;
	    sub->enable = csrq.publishingEnabled;
	    Tcl_InitHashTable(&sub->monitors, TCL_ONE_WORD_KEYS);
	    csr = UA_Client_Subscriptions_create(uah->client, csrq, sub,
						 ChangeSubCB, ReleaseSubCB);
	    uaret = csr.responseHeader.serviceResult;
	    if (uaret == UA_STATUSCODE_GOOD) {
		int new;

		sub->subid = csr.subscriptionId;
		sub->interval = csr.revisedPublishingInterval;
		sub->ltCount = csr.revisedLifetimeCount;
		sub->kaCount = csr.revisedMaxKeepAliveCount;
		hPtr = Tcl_CreateHashEntry(&uah->subs,
					   INT2PTR(sub->subid), &new);
		if (!new) {
		    UAS *oldsub = (UAS *) Tcl_GetHashValue(hPtr);
		    Tcl_HashEntry *hPtr2;
		    Tcl_HashSearch search;

		    hPtr2 = Tcl_FirstHashEntry(&oldsub->monitors, &search);
		    while (hPtr2 != NULL) {
			UAM *mon = (UAM *) Tcl_GetHashValue(hPtr2);
................................................................................
	    if (mon->cmdObjs != NULL) {
		ckfree((char *) mon->cmdObjs);
	    }
	    ckfree((char *) mon);
	    ReportError(interp, uai, "CreateMonitor", uaret);
	    return TCL_ERROR;
	} else {
	    int new;

	    mon->interval = mon->interval;
	    mon->monid = mr.monitoredItemId;
	    hPtr = Tcl_CreateHashEntry(&sub->monitors,
				       INT2PTR(mon->monid), &new);
	    if (!new) {
		UAM *oldmon = (UAM *) Tcl_GetHashValue(hPtr);

		DeleteMonCB(uah->client, subid, sub, oldmon->monid, oldmon);
	    }
	    Tcl_SetHashValue(hPtr, mon);
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(mon->monid));
	}
................................................................................
    UA_StatusCode uaret;
    char *p;
    static const char *kindNames[] = {
	"Namespace",
	"Variable", "VariableType", "Object",
	"View", "ObjectType", "DataType", "ReferenceType",
	"Reference", "Method",

	NULL
    };
    enum NodeKind {
	NK_Namespace,
	NK_Variable, NK_VariableType, NK_Object,
	NK_View, NK_ObjectType, NK_DataType, NK_ReferenceType,
	NK_Reference, NK_Method

    };

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "handle kind ...");
	return TCL_ERROR;
    }
    hPtr = Tcl_FindHashEntry(&uai->handles, Tcl_GetString(objv[1]));
................................................................................
	/* At least preset the DisplayName attribute from browse name. */
	if (a->displayName.text.length == 0) {
	    UA_deleteMembers(&a->displayName.text,
			     &UA_TYPES[UA_TYPES_STRING]);
	    UA_String_copy(&qn.name, &a->displayName.text);
	}
	if (objc > 9) {
	    int nCmdObjs, i, new;
	    Tcl_Obj **cmdObjs;
	    UAQ *uaq;
	    static const UA_DataSource dsrc = {
		DataSourceRead, DataSourceWrite
	    };

	    if (Tcl_ListObjGetElements(interp, objv[9], &nCmdObjs, &cmdObjs)
................................................................................
	     */
	    uaq = (UAQ *) ckalloc(sizeof(UAQ));
	    memset(uaq, 0, sizeof(UAQ));
	    uaq->uah = NULL;
	    uaq->outType = NULL;
	    uaq->nCmdObjs = uaq->nSavedCmdObjs = 0;
	    uaq->cmdObjs = uaq->savedCmdObjs = NULL;

	    uaret = UA_Server_addDataSourceVariableNode(uah->server,
			nodeid, parent, refid, qn, typeid,
			*((const UA_VariableAttributes *) a), dsrc, uaq,
			&retid);
	    if (uaret != UA_STATUSCODE_GOOD) {
		for (i = 0; i < uaq->nSavedCmdObjs; i++) {
		    Tcl_DecrRefCount(uaq->savedCmdObjs[i]);
		}
		ckfree((char *) uaq);
	    } else {
		Tcl_DString ds;
		char *p;

		p = PrintNodeId(&retid, &ds);
		hPtr = Tcl_CreateHashEntry(&uah->dsrcs, p, &new);
		Tcl_DStringFree(&ds);
		if (!new) {
		    UAQ *oldq = (UAQ *) Tcl_GetHashValue(hPtr);






		    for (i = 0; i < oldq->nSavedCmdObjs; i++) {
			Tcl_DecrRefCount(oldq->savedCmdObjs[i]);
		    }












		    ckfree((char *) oldq);
		}
		Tcl_SetHashValue(hPtr, (ClientData) uaq);
		/*
		 * Initialize the callback information.
		 */
		uaq->uah = uah;
................................................................................
refError:
	UA_NodeId_deleteMembers(&nodeid);
	UA_NodeId_deleteMembers(&refid);
	UA_NodeId_deleteMembers(&destid);
	break;
    }

    case NK_Method: {

	UA_NodeId nodeid, parent, refid, retid;
	UA_QualifiedName qn;
	const UA_MethodAttributes *a0 = &UA_MethodAttributes_default;
	UA_MethodAttributes *a = NULL;
	int ian, oan, nCmdObjs;
	Tcl_Obj **cmdObjs;
	UA_Argument *ia = NULL, *oa = NULL;
................................................................................
	    goto methError;
	}
	p = Tcl_GetString(objv[5]);
	if ((*p != '\0') &&
	    (ParseRefTypeOrNodeId(interp, uai, &refid, p) == NULL)) {
	    goto methError;
	}
	oa = GetMethodArgs(interp, uah, objv[6], &oan);
	if (oan < 0) {
	    goto methError;
	}
	if (ParseQualifiedName(interp, &qn, Tcl_GetString(objv[7])) == NULL) {
	    goto methError;
	}
	ia = GetMethodArgs(interp, uah, objv[8], &ian);
	if (ian < 0) {
	    goto methError;
	}
	if (Tcl_ListObjGetElements(interp, objv[9], &nCmdObjs, &cmdObjs)
	    != TCL_OK) {
	    goto methError;
	}
................................................................................
	}
	/* At least preset the DisplayName attribute from browse name. */
	if (a->displayName.text.length == 0) {
	    UA_deleteMembers(&a->displayName.text,
			     &UA_TYPES[UA_TYPES_STRING]);
	    UA_String_copy(&qn.name, &a->displayName.text);
	}
	if (oan > 1) {
	    Tcl_SetResult(interp, "only one output argument supported",
			  TCL_STATIC);
	    goto methError;
	}
	if (oan > 0) {
	    oatype = NULL;
	    if ((oa[0].dataType.namespaceIndex != 0) &&
................................................................................
		Tcl_SetResult(interp, "unsupported output argument type",
			      TCL_STATIC);
		goto methError;
	    }
	}
	uaq = (UAQ *) ckalloc(sizeof(UAQ));
	memset(uaq, 0, sizeof(UAQ));





	uaret = UA_Server_addMethodNode(uah->server, nodeid, parent,
					refid, qn, *a, MethodInvoke, ian, ia,
					oan, oa, uaq, &retid);
	if (uaret != UA_STATUSCODE_GOOD) {
	    ReportError(interp, uai, "AddMethod", uaret);
	} else {
	    Tcl_DString ds;
	    char *p;
	    int i, new;

	    p = PrintNodeId(&retid, &ds);
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(p, -1));
	    hPtr = Tcl_CreateHashEntry(&uah->meths, p, &new);
	    if (!new) {
		UAQ *oldq = (UAQ *) Tcl_GetHashValue(hPtr);






		for (i = 0; i < oldq->nSavedCmdObjs; i++) {
		    Tcl_DecrRefCount(oldq->savedCmdObjs[i]);
		}












		ckfree((char *) oldq);
	    }
	    Tcl_SetHashValue(hPtr, (ClientData) uaq);
	    uaq->uah = uah;
	    uaq->outType = oatype;
	    uaq->nCmdObjs = nCmdObjs;
	    uaq->cmdObjs = (Tcl_Obj **) ckalloc(nCmdObjs * sizeof(Tcl_Obj *));
................................................................................
	    UA_NodeId_deleteMembers(&destid);
	    return TCL_ERROR;
	}
	UA_NodeId_deleteMembers(&nodeid);
	UA_NodeId_deleteMembers(&refid);
	UA_NodeId_deleteMembers(&destid);
    } else {
	int withRefs = UA_FALSE;



	if (objc > 5) {
	    Tcl_WrongNumArgs(interp, 1, objv, "handle Node id ?withrefs?");
	    return TCL_ERROR;
	}
	UA_NodeId_init(&nodeid);
	if (ParseNodeId(interp, &nodeid, Tcl_GetString(objv[3])) == NULL) {
................................................................................
	uaret = UA_Server_deleteNode(uah->server, nodeid, withRefs);
	if (uaret != UA_STATUSCODE_GOOD) {
	    ReportError(interp, uai, "DeleteNode", uaret);
nodeError:
	    UA_NodeId_deleteMembers(&nodeid);
	    return TCL_ERROR;
	}









































	UA_NodeId_deleteMembers(&nodeid);
    }
    return TCL_OK;
}
 
/*
 *-------------------------------------------------------------------------
................................................................................

static int
MethodsObjCmd(ClientData clientData, Tcl_Interp *interp,
	      int objc, Tcl_Obj * const objv[])
{
    UAI *uai = (UAI *) clientData;
    UAH *uah;

    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    Tcl_Obj *list;


    if (objc != 2) {

	Tcl_WrongNumArgs(interp, 1, objv, "handle");
	return TCL_ERROR;
    }
    hPtr = Tcl_FindHashEntry(&uai->handles, Tcl_GetString(objv[1]));
    if (hPtr == NULL) {
	Tcl_SetResult(interp, "handle not found", TCL_STATIC);
	ReportError(interp, uai, NULL, 0);
	return TCL_ERROR;
    }
    uah = (UAH *) Tcl_GetHashValue(hPtr);
    hPtr = Tcl_FirstHashEntry(&uah->meths, &search);

    list = Tcl_NewListObj(0, NULL);



    while (hPtr != NULL) {
	UAQ *uaq = (UAQ *) Tcl_GetHashValue(hPtr);

	Tcl_ListObjAppendElement(NULL, list,
	    Tcl_NewStringObj(Tcl_GetHashKey(&uah->meths, hPtr), -1));



	Tcl_ListObjAppendElement(NULL, list,
	    Tcl_NewStringObj((uaq->outType == NULL) ? "" :

			     uaq->outType->typeName, -1));



	Tcl_ListObjAppendElement(NULL, list,
	    Tcl_NewListObj(uaq->nSavedCmdObjs, uaq->savedCmdObjs));
	/* TBD: add input argument list */
	hPtr = Tcl_NextHashEntry(&search);
    }
    Tcl_SetObjResult(interp, list);
















































    return TCL_OK;
}
 
/*
 *-------------------------------------------------------------------------
 *
 * DataSourcesObjCmd --
................................................................................
	} else {
	    size_t typesSize = uah->typesSize;
	    UA_DataType *types = uah->types;

	    uah->typesSize = 0;
	    uah->types = NULL;
	    ReleaseTypes(uah, 1);
	    ReleaseMethodsEtc(uah, 1);
#if (UA_OPEN62541_VER_MAJOR < 1)
	    if (uah->client != NULL) {
		UA_Client_setCustomDataTypes(uah->client, types, typesSize);
	    } else {
		UA_Server_setCustomDataTypes(uah->server, types, typesSize);
	    }
#else
................................................................................
	    return TCL_ERROR;
	}
	if (ParseNodeId(interp, &encid, Tcl_GetString(objv[5])) == NULL) {
	    return TCL_ERROR;
	}
	/* Validate member types. */
	for (i = 6; i < objc; i += 2) {






	    type = FindType(uai, uah, 1, Tcl_GetString(objv[i]));
	    if (type == NULL) {
		Tcl_SetResult(interp, "unknown type in struct", TCL_STATIC);
		ReportError(interp, uai, NULL, 0);
		return TCL_ERROR;
	    }		
	}
	max = nodeid.identifier.numeric;
	if (max < encid.identifier.numeric) {
	    max = encid.identifier.numeric;
	}
	max += 64;
	if (uah->types == NULL) {
................................................................................
	uah->types[i].pointerFree = 1;
	uah->types[i].overlayable = 0;
	uah->types[i].binaryEncodingId = encid.identifier.numeric;
	j = sizeof(UA_DataTypeMember) * uah->types[i].membersSize;
	uah->types[i].members = ckalloc(j);
	memset(uah->types[i].members, 0, j);
	for (j = 6; j < objc; j += 2) {
	    int k, isZero;







	    type = FindType(uai, uah, 1, Tcl_GetString(objv[j]));
	    if ((type >= &UA_TYPES[0]) && (type < &UA_TYPES[UA_TYPES_COUNT])) {
		isZero = 1;
	    } else {
		isZero = 0;
	    }
	    k = (j - 6) / 2;
	    obj = GetSymObj(uai, Tcl_GetString(objv[j + 1]),
................................................................................
	    uah->types[i].members[k].memberTypeIndex = type->typeIndex;
	    uah->types[i].members[k].padding =
		sizeof(double) - (uah->types[i].memSize % sizeof(double));
	    if (uah->types[i].members[k].padding == sizeof(double)) {
		uah->types[i].members[k].padding = 0;
	    }
	    uah->types[i].members[k].namespaceZero = isZero;
	    uah->types[i].members[k].isArray = 0;
	    if (!type->pointerFree) {
		uah->types[i].pointerFree = 0;
	    }
	    uah->types[i].memSize +=
		type->memSize + uah->types[i].members[k].padding;
	}
	break;
................................................................................
 *-------------------------------------------------------------------------
 */

int DLLEXPORT
Topcua_Init(Tcl_Interp *interp)
{
    UAI *uai;
    int i, skip, new;
    Tcl_HashEntry *hPtr;
    Tcl_Namespace *nsPtr;
    Tcl_Obj *cmdList;
    Tcl_Command cmd;

    /*
     * UA data types for which a mapping is provided.


     */
    static const int types[] = {
	UA_TYPES_BOOLEAN, UA_TYPES_SBYTE, UA_TYPES_BYTE,
	UA_TYPES_INT16, UA_TYPES_UINT16, UA_TYPES_INT32,
	UA_TYPES_UINT32, UA_TYPES_INT64, UA_TYPES_UINT64,
	UA_TYPES_FLOAT, UA_TYPES_DOUBLE, UA_TYPES_STRING,
	UA_TYPES_DATETIME, UA_TYPES_GUID, UA_TYPES_BYTESTRING,
	UA_TYPES_XMLELEMENT, UA_TYPES_NODEID, UA_TYPES_EXPANDEDNODEID,
	UA_TYPES_STATUSCODE, UA_TYPES_QUALIFIEDNAME,
	UA_TYPES_LOCALIZEDTEXT, UA_TYPES_EXTENSIONOBJECT,
	UA_TYPES_DATAVALUE, UA_TYPES_VARIANT,
	UA_TYPES_ARGUMENT, UA_TYPES_DIAGNOSTICINFO


    };


    /*
     * Aliased types as found with
     *   grep "#define.*UA_TYPES_.*UA_TYPES_" open62541/open62541.h
     */
    static const struct {
	const char *name;
	int type;
    } typeAliases[] = {
	{ "UtcTime", UA_TYPES_DATETIME },
	{ "LocaleId", UA_TYPES_STRING },
	{ "Duration", UA_TYPES_DOUBLE }
    };


    /*
     * Namespace commands to create.
     */
    static const struct {
	const char *name;
	Tcl_ObjCmdProc *proc;
................................................................................

    /*
     * Basic (scalar) types we can map.
     */
    Tcl_InitHashTable(&uai->types, TCL_STRING_KEYS);
    for (i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
	hPtr = Tcl_CreateHashEntry(&uai->types,
				   UA_TYPES[types[i]].typeName, &new);
	Tcl_SetHashValue(hPtr, &UA_TYPES[i]);
    }

    for (i = 0; i < sizeof(typeAliases) / sizeof(typeAliases[0]); i++) {
	hPtr = Tcl_CreateHashEntry(&uai->types,
				   typeAliases[i].name, &new);
	Tcl_SetHashValue(hPtr, &UA_TYPES[typeAliases[i].type]);
    }


    /*
     * Reference types.
     */
    Tcl_InitHashTable(&uai->refTypes, TCL_STRING_KEYS);
    for (i = 0; i < sizeof(refTypes) / sizeof(refTypes[0]); i++) {
	hPtr = Tcl_CreateHashEntry(&uai->refTypes, refTypes[i].name, &new);
	Tcl_SetHashValue(hPtr, INT2PTR(refTypes[i].ns0id));
    }

    /*
     * Map of symbols used e.g. in data represented as dict.
     */
    Tcl_InitHashTable(&uai->syms, TCL_STRING_KEYS);







|






>







 







|







 







<




|
>
>
>
>
>







 







>
>
>





|
>
>
>
>
>




>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|

|
|







 







|
>


|
>







|
>







 







|


|
|

|

>

|
|

|

<
<
>
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
>







|
>




|
>

>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



>
>
>







 







|
|

|
|
<
>
|
<
<


<
<
<
>
|

>
|
|
<










|
|
|











>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
>





>








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






|


|


|



|
>
>
>
>
|




|





>







 







>



>
>
>
>
>
>
>
>







 







>







 







>
>



>
>
>
>
>
>
>
>







 







>







 







|







 







|





|

|







 







|
|







 







|
>







 







>







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|

|
|
|
>










>
>
>
|
>







 







|






|
|







 







|




|
|







 







>






|
>







 







|







 







>





<
<
<






|

|


>
>
>
>
>
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>







 







|
>







 







|






|







 







|







 







>
>
>
>
>








|



|
|


>
>
>
>
>
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>







 







|
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>


<
>

<
>
|










>
|
>
>
>
|
|
<
|
|
>
>
>
|
|
>
|
>
>
>
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<







 







>
>
>
>
>
>
|




|







 







|
>

>
>
>
>
>
|







 







|







 







|







>
>










|
|
>
>


>












>







 







|


>


|


>






|







65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
...
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
...
962
963
964
965
966
967
968

969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
...
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
....
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
....
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
....
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
....
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642


2643
2644
















2645
2646
2647
2648
2649
2650
2651
....
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
....
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014

3015
3016


3017
3018



3019
3020
3021
3022
3023
3024

3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
....
3082
3083
3084
3085
3086
3087
3088



































3089
3090
3091
3092
3093
3094
3095
....
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
....
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
....
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
....
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
....
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
....
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
....
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
....
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
....
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
....
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
....
5630
5631
5632
5633
5634
5635
5636
5637
5638
5639
5640
5641
5642
5643
5644
5645
5646
5647
5648
5649
5650
5651
5652
5653
5654
5655
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
5682
5683
5684
5685
5686
5687
5688
5689
5690
5691
5692
5693
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
5708
5709
5710
5711
5712
5713
5714
5715
5716
5717
5718
5719
5720
5721
5722
5723
5724
5725
5726
5727
....
6030
6031
6032
6033
6034
6035
6036
6037
6038
6039
6040
6041
6042
6043
6044
6045
6046
6047
6048
6049
6050
6051
6052
....
6361
6362
6363
6364
6365
6366
6367
6368
6369
6370
6371
6372
6373
6374
6375
6376
6377
6378
6379
6380
6381
....
6828
6829
6830
6831
6832
6833
6834
6835
6836
6837
6838
6839
6840
6841
6842
6843
6844
6845
6846
6847
6848
6849
6850
....
6958
6959
6960
6961
6962
6963
6964
6965
6966
6967
6968
6969
6970
6971
6972
....
6980
6981
6982
6983
6984
6985
6986
6987
6988
6989
6990
6991
6992



6993
6994
6995
6996
6997
6998
6999
7000
7001
7002
7003
7004
7005
7006
7007
7008
7009
7010
7011
7012
7013
7014
7015
7016
7017
7018
7019
7020
7021
7022
7023
7024
7025
7026
7027
7028
7029
7030
....
7218
7219
7220
7221
7222
7223
7224
7225
7226
7227
7228
7229
7230
7231
7232
7233
....
7252
7253
7254
7255
7256
7257
7258
7259
7260
7261
7262
7263
7264
7265
7266
7267
7268
7269
7270
7271
7272
7273
....
7284
7285
7286
7287
7288
7289
7290
7291
7292
7293
7294
7295
7296
7297
7298
....
7323
7324
7325
7326
7327
7328
7329
7330
7331
7332
7333
7334
7335
7336
7337
7338
7339
7340
7341
7342
7343
7344
7345
7346
7347
7348
7349
7350
7351
7352
7353
7354
7355
7356
7357
7358
7359
7360
7361
7362
7363
7364
7365
7366
7367
7368
7369
7370
7371
7372
7373
7374
7375
7376
7377
....
7501
7502
7503
7504
7505
7506
7507
7508
7509
7510
7511
7512
7513
7514
7515
7516
7517
....
7524
7525
7526
7527
7528
7529
7530
7531
7532
7533
7534
7535
7536
7537
7538
7539
7540
7541
7542
7543
7544
7545
7546
7547
7548
7549
7550
7551
7552
7553
7554
7555
7556
7557
7558
7559
7560
7561
7562
7563
7564
7565
7566
7567
7568
7569
7570
7571
7572
7573
7574
7575
7576
7577
7578
....
7592
7593
7594
7595
7596
7597
7598
7599
7600
7601

7602
7603

7604
7605
7606
7607
7608
7609
7610
7611
7612
7613
7614
7615
7616
7617
7618
7619
7620
7621
7622

7623
7624
7625
7626
7627
7628
7629
7630
7631
7632
7633
7634
7635
7636
7637
7638
7639
7640
7641
7642
7643
7644
7645
7646
7647
7648
7649
7650
7651
7652
7653
7654
7655
7656
7657
7658
7659
7660
7661
7662
7663
7664
7665
7666
7667
7668
7669
7670
7671
7672
7673
7674
7675
7676
7677
7678
7679
7680
7681
7682
7683
7684
7685
7686
7687
7688
7689
7690
7691
7692
7693
7694
7695
....
8038
8039
8040
8041
8042
8043
8044

8045
8046
8047
8048
8049
8050
8051
....
8153
8154
8155
8156
8157
8158
8159
8160
8161
8162
8163
8164
8165
8166
8167
8168
8169
8170
8171
8172
8173
8174
8175
8176
8177
8178
....
8206
8207
8208
8209
8210
8211
8212
8213
8214
8215
8216
8217
8218
8219
8220
8221
8222
8223
8224
8225
8226
8227
8228
....
8231
8232
8233
8234
8235
8236
8237
8238
8239
8240
8241
8242
8243
8244
8245
....
8335
8336
8337
8338
8339
8340
8341
8342
8343
8344
8345
8346
8347
8348
8349
8350
8351
8352
8353
8354
8355
8356
8357
8358
8359
8360
8361
8362
8363
8364
8365
8366
8367
8368
8369
8370
8371
8372
8373
8374
8375
8376
8377
8378
8379
8380
8381
8382
8383
8384
8385
8386
8387
8388
....
8443
8444
8445
8446
8447
8448
8449
8450
8451
8452
8453
8454
8455
8456
8457
8458
8459
8460
8461
8462
8463
8464
8465
8466
8467
8468
8469
8470
8471
8472
8473
} UAM;

/*
 * Structure representing a method invocation. This is used for
 * both method callbacks and data source callbacks.
 */

typedef struct UAQ {
    struct UAH *uah;			/* Containing server handle. */
    const UA_DataType *outType;		/* Output data type or NULL. */
    int nCmdObjs;			/* For the Tcl callback. */
    Tcl_Obj **cmdObjs;			/* Ditto. */
    int nSavedCmdObjs;			/* Ditto. */
    Tcl_Obj **savedCmdObjs;		/* Ditto. */
    struct UAQ **backRef;		/* For delete within callback. */
} UAQ;

/*
 * Structure representing an OPC/UA client or server.
 */

typedef struct UAH {
................................................................................
					  UA_ReferenceDescription *ref);
static Tcl_Obj *	DecodeToObj(UAI *uai, const UA_DataType *type,
				    const void *data, int depth);
static void *		EncodeFromObj(Tcl_Interp *interp, UAI *uai, UAH *uah,
				      const UA_DataType *type,
				      const void *preset, Tcl_Obj *obj,
				      int depth);
static UA_Argument *	GetMethodArgs(Tcl_Interp *interp, UAH *uah, int simple,
				      Tcl_Obj *obj, int *numRet);
static UA_StatusCode	MethodInvoke(UA_Server *server,
				     const UA_NodeId *sessionId,
				     void *sessionContext,
				     const UA_NodeId *methodId,
				     void *methodContext,
				     const UA_NodeId *objectId,
................................................................................
 */

static void
ReleaseMethodsEtc(UAH *uah, int killProcs)
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;

    int i;

    hPtr = Tcl_FirstHashEntry(&uah->meths, &search);
    while (hPtr != NULL) {
	UAQ *meth = (UAQ *) Tcl_GetHashValue(hPtr);

	if (meth->backRef != NULL) {
	    *(meth->backRef) = NULL;
	    meth->backRef = NULL;
	}
	Tcl_DeleteHashEntry(hPtr);
	if (killProcs) {
	    Tcl_Obj *objs[4];

	    /*
	     * Evaluates to "::namespace eval NS { ::rename P {} }"
	     * ignoring errors.
................................................................................
	    for (i = 0; i < 4; i++) {
		Tcl_DecrRefCount(objs[i]);
	    }
	}
	for (i = 0; i < meth->nCmdObjs; i++) {
	    Tcl_DecrRefCount(meth->cmdObjs[i]);
	}
	if (meth->cmdObjs != NULL) {
	    ckfree((char *) meth->cmdObjs);
	}
	ckfree((char *) meth);
	hPtr = Tcl_NextHashEntry(&search);
    }
    hPtr = Tcl_FirstHashEntry(&uah->dsrcs, &search);
    while (hPtr != NULL) {
	UAQ *dsrc = (UAQ *) Tcl_GetHashValue(hPtr);

	if (dsrc->backRef != NULL) {
	    *(dsrc->backRef) = NULL;
	    dsrc->backRef = NULL;
	}
	Tcl_DeleteHashEntry(hPtr);
	for (i = 0; i < dsrc->nCmdObjs; i++) {
	    Tcl_DecrRefCount(dsrc->cmdObjs[i]);
	}
	if (dsrc->cmdObjs != NULL) {
	    ckfree((char *) dsrc->cmdObjs);
	}
	ckfree((char *) dsrc);
	hPtr = Tcl_NextHashEntry(&search);
    }
}
 
/*
 *-------------------------------------------------------------------------
................................................................................
ReleaseTypes(UAH *uah, int all)
{
    UAI *uai = uah->uai;
    size_t i, j;
    Tcl_Obj *obj;

    if (uah->types != NULL) {
	Tcl_HashEntry *hPtr;
	Tcl_HashSearch search;

	hPtr = Tcl_FirstHashEntry(&uah->meths, &search);
	while (hPtr != NULL) {
	    UAQ *uaq = (UAQ *) Tcl_GetHashValue(hPtr);

	    if (uaq->outType != NULL) {
		j = uaq->outType - uah->types;
		if ((j >= 0) && (j < uah->typesSize)) {
		    uaq->outType = NULL;
		}
	    }
	    hPtr = Tcl_NextHashEntry(&search);
	}
	for (i = 0; i < uah->typesSize; i++) {
	    for (j = 0; j < uah->types[i].membersSize; j++) {
		if (uah->types[i].members[j].memberName != NULL) {
		    obj = GetSymObj(uai, uah->types[i].members[j].memberName,
				    NULL);
		    Tcl_DecrRefCount(obj);
		}
................................................................................
 */

static Tcl_Obj *
GetSymObj(UAI *uai, const char *string, char **keyRet)
{
    Tcl_Obj *obj;
    Tcl_HashEntry *hPtr;
    int isNew;

    hPtr = Tcl_CreateHashEntry(&uai->syms, string, &isNew);
    if (isNew) {
	obj = Tcl_NewStringObj(string, -1);
	Tcl_IncrRefCount(obj);
	Tcl_SetHashValue(hPtr, (ClientData) obj);
    } else {
	obj = (Tcl_Obj *) Tcl_GetHashValue(hPtr);
    }
    if (keyRet != NULL) {
................................................................................
    }
    if (type == &UA_TYPES[UA_TYPES_UINT64]) {
	return Tcl_NewWideIntObj(((Tcl_WideInt *) data)[0]);
    }
    if (type == &UA_TYPES[UA_TYPES_FLOAT]) {
	return Tcl_NewDoubleObj(((float *) data)[0]);
    }
    if ((type == &UA_TYPES[UA_TYPES_DOUBLE]) ||
	(type == &UA_TYPES[UA_TYPES_DURATION])) {
	return Tcl_NewDoubleObj(((double *) data)[0]);
    }
    if ((type == &UA_TYPES[UA_TYPES_STRING]) ||
	(type == &UA_TYPES[UA_TYPES_LOCALEID])) {
	UA_String *v = (UA_String *) data;

	if (v->data != NULL) {
	    return Tcl_NewStringObj((char *) v->data, v->length);
	}
	return Tcl_NewObj();
    }
    if ((type == &UA_TYPES[UA_TYPES_DATETIME]) ||
	(type == &UA_TYPES[UA_TYPES_UTCTIME])) {
	UA_DateTime t = ((UA_DateTime *) data)[0];

	return Tcl_NewWideIntObj((Tcl_WideInt) t);
    }
    if (type == &UA_TYPES[UA_TYPES_GUID]) {
	UA_Guid *v = (UA_Guid *) data;
	char buffer[64];
................................................................................

	obj = Tcl_NewStringObj(PrintQualifiedName(v, &ds), -1);
	Tcl_DStringFree(&ds);
	return obj;
    }
    if (type == &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]) {
	UA_LocalizedText *v = (UA_LocalizedText *) data;
	Tcl_Obj *obj, *dict = Tcl_NewDictObj();

	if (v->locale.data != NULL) {
	    obj = Tcl_NewStringObj((char * ) v->locale.data,
				   v->locale.length);
	} else {
	    obj = Tcl_NewObj();
	}
	Tcl_DictObjPut(NULL, dict, GetSymObj(uai, "locale", NULL), obj);
	if (v->text.data != NULL) {
	    obj = Tcl_NewStringObj((char * ) v->text.data,
				   v->text.length);
	} else {
	    obj = Tcl_NewObj();
	}


	Tcl_DictObjPut(NULL, dict, GetSymObj(uai, "text", NULL), obj);
	return dict;
















    }
    if (type == &UA_TYPES[UA_TYPES_DATAVALUE]) {
	UA_DataValue *v = (UA_DataValue *) data;
	Tcl_Obj *dict = Tcl_NewDictObj();

	if (v->hasValue || empty) {
	    Tcl_Obj *vv;
................................................................................
	double v;

	if (Tcl_GetDoubleFromObj(interp, obj, &v) != TCL_OK) {
	    goto error;
	}
	value = UA_new(type);
	((UA_Float *) value)[0] = v;
    } else if ((type == &UA_TYPES[UA_TYPES_DOUBLE]) ||
	       (type == &UA_TYPES[UA_TYPES_DURATION])) {
	double v;

	if (Tcl_GetDoubleFromObj(interp, obj, &v) != TCL_OK) {
	    goto error;
	}
	value = UA_new(type);
	((UA_Double *) value)[0] = v;
    } else if ((type == &UA_TYPES[UA_TYPES_STRING]) ||
	       (type == &UA_TYPES[UA_TYPES_LOCALEID])) {
	UA_String s = UA_String_fromChars(Tcl_GetString(obj));

	value = UA_new(type);
	UA_String_copy(&s, (UA_String *) value);
    } else if ((type == &UA_TYPES[UA_TYPES_DATETIME]) ||
	       (type == &UA_TYPES[UA_TYPES_UTCTIME])) {
	Tcl_WideInt v;
#if (UA_OPEN62541_VER_MAJOR >= 1)
	int x[6];
	char ch;

	if ((sscanf(Tcl_GetString(obj), "%4d-%2d-%2dT%2d:%2d:%2d%c",
		    x + 0, x + 1, x + 2, x + 4, x + 5, x + 6, &ch) == 7) &&
	    (ch == 'Z')) {
	    UA_DateTimeStruct dts;
	    UA_DateTime dt;

	    memset(&dts, 0, sizeof(dts));
	    dts.year = x[0];
	    dts.month = x[1];
	    dts.day = x[2];
	    dts.hour = x[3];
	    dts.min = x[4];
	    dts.sec = x[5];
	    dt = UA_DateTime_fromStruct(dts);
	    v = dt;
	    goto haveDateTime;
	}
#endif
	if (Tcl_GetWideIntFromObj(interp, obj, &v) != TCL_OK) {
	    goto error;
	}
#if (UA_OPEN62541_VER_MAJOR >= 1)
haveDateTime:
#endif
	value = UA_new(type);
	((UA_DateTime *) value)[0] = (UA_DateTime) v;
    } else if (type == &UA_TYPES[UA_TYPES_GUID]) {
	UA_Guid *g;
	int d[11];

	if (sscanf(Tcl_GetString(obj),
................................................................................
	if (ParseQualifiedName(interp, (UA_QualifiedName *) value,
			       Tcl_GetString(obj)) == NULL) {
	    UA_delete(value, type);
	    value = NULL;
	    goto error;
	}
    } else if (type == &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]) {
	UA_String s;
	Tcl_Obj *locale = NULL, *text = NULL;

	Tcl_DictObjGet(NULL, obj, GetSymObj(uai, "locale", NULL), &locale);
	Tcl_DictObjGet(NULL, obj, GetSymObj(uai, "text", NULL), &text);

	if (text == NULL) {
	    text = obj;


	}
	value = UA_new(type);



	if (locale != NULL) {
	    s = UA_String_fromChars(Tcl_GetString(locale));
	    UA_String_copy(&s, &((UA_LocalizedText *) value)->locale);
	}
	s = UA_String_fromChars(Tcl_GetString(text));
	UA_String_copy(&s, &((UA_LocalizedText *) value)->text);

    } else if (type == &UA_TYPES[UA_TYPES_VARIANT]) {
	int n, j;
	Tcl_Obj **elem;
	const UA_DataType *vtype = NULL;
	void *vdata = NULL;

	if (Tcl_ListObjGetElements(interp, obj, &n, &elem) != TCL_OK) {
	    goto error;
	}
	if (n == 1) {
	    /* Fallback to String type. */
	    vtype = FindType(uai, uah, 0, "String");
	    goto writeSingle;
	}
	if (n) {
	    vtype = FindType(uai, uah, 0, Tcl_GetString(elem[0]));
	    if ((vtype == NULL) || (vtype == &UA_TYPES[UA_TYPES_VARIANT])) {
		Tcl_SetResult(interp, "unsupported type", TCL_STATIC);
		goto error;
	    }
	    /* Now skip type element. */
	    elem += 1;
	    n -= 1;
	    if (n == 1) {
writeSingle:
		vdata = EncodeFromObj(interp, uai, uah, vtype,
				      NULL, elem[0], depth + 1);
	    } else {
		vdata = UA_Array_new(n, vtype);
		if (vdata == NULL) {
		    Tcl_SetResult(interp, "out of memory", TCL_STATIC);
		    goto error;
................................................................................
	if (vtype != NULL) {
	    if (n == 1) {
		UA_Variant_setScalar(value, vdata, vtype);
	    } else if (n > 1) {
		UA_Variant_setArray(value, vdata, n, vtype);
	    }
	}



































    } else if (type->membersSize > 0) {
	int i;
	void *data;

	value = UA_new(type);
	if (preset != NULL) {
	    UA_copy(preset, value, type);
................................................................................
 * Side effects:
 *	Allocates memory for UA_Argument array.
 *
 *-------------------------------------------------------------------------
 */

static UA_Argument *
GetMethodArgs(Tcl_Interp *interp, UAH *uah, int simple,
	      Tcl_Obj *obj, int *numRet)
{
    UAI *uai = uah->uai;
    int i, n;
    Tcl_Obj **elem;
    UA_Argument *a;
    char *p;

    *numRet = -1;	/* Preset to error. */
    if (Tcl_ListObjGetElements(interp, obj, &n, &elem) != TCL_OK) {
	return NULL;
    }
    if (n == 0) {
	*numRet = 0;	/* Good case. */
	return NULL;
    }
    if (!simple) {
	a = UA_Array_new(n / 2, &UA_TYPES[UA_TYPES_ARGUMENT]);
	for (i = 0, p = (char *) a; i < n; i++) {
	    UA_Argument *v;

	    v = EncodeFromObj(interp, uai, uah, &UA_TYPES[UA_TYPES_ARGUMENT],
			      NULL, elem[i], 0);
	    if (v == NULL) {
		UA_Array_delete(a, n, &UA_TYPES[UA_TYPES_ARGUMENT]);
		return NULL;
	    }
	    UA_copy(v, p, &UA_TYPES[UA_TYPES_ARGUMENT]);
	    p += UA_TYPES[UA_TYPES_ARGUMENT].memSize;
	}
	*numRet = n;
	return a;
    }
    if (n % 2 != 0) {
	Tcl_SetResult(interp, "need even number of elements", TCL_STATIC);
	return NULL;
    }
    a = UA_Array_new(n / 2, &UA_TYPES[UA_TYPES_ARGUMENT]);
    for (i = 0, p = (char *) a; i < n; i += 2) {
	UA_Argument *v;
	Tcl_HashEntry *hPtr;
	char *tn;
	UA_String s;

	v = (UA_Argument *) p;
	v->valueRank = -2;	/* Allow scalar or array. */

	/* Abbrevation: allow names from types hash table. */
	tn = Tcl_GetString(elem[0]);
	if (*tn == '*') {
	    v->valueRank = 0;	/* Force to array. */
	    tn++;
	}
	hPtr = Tcl_FindHashEntry(&uai->types, tn);
	if (hPtr != NULL) {
	    UA_DataType *type = (UA_DataType *) Tcl_GetHashValue(hPtr);

	    UA_NodeId_copy(&type->typeId, &v->dataType);
	} else if (ParseTypeOrNodeId(interp, uah, &v->dataType, tn) == NULL) {
	    UA_Array_delete(a, n / 2, &UA_TYPES[UA_TYPES_ARGUMENT]);
	    return NULL;
	}
	s = UA_String_fromChars(Tcl_GetString(elem[1]));
	UA_String_copy(&s, &v->name);
	p += UA_TYPES[UA_TYPES_ARGUMENT].memSize;
    }
    *numRet = n / 2;
    return a;
}
 
/*
 *-------------------------------------------------------------------------
................................................................................
    Tcl_DStringFree(&ds);
    for (i = 0; i < inputSize; i++) {
	const UA_Variant *v = &input[i];

	Tcl_ListObjAppendElement(NULL, list,
	    DecodeToObj(uai, &UA_TYPES[UA_TYPES_VARIANT], v, 0));
    }
    meth->backRef = &meth;
    Tcl_IncrRefCount(list);
    ret = Tcl_GlobalEvalObj(interp, list);
    Tcl_DecrRefCount(list);
    if (meth == NULL) {
	/*
	 * Node was deleted, we're finished.
	 */
	goto done;
    }
    meth->backRef = NULL;

    /*
     * Special cases:
     *   TCL_BREAK makes into an array result.
     *   When there's no output type, the result is void.
     */
    if ((meth->outType == NULL) && ((ret == TCL_BREAK) || (ret == TCL_OK))) {
	/* Void result. */
................................................................................
	    Tcl_DecrRefCount(cmdObjs[i]);
	}
	ckfree((char *) cmdObjs);
    } else {
	meth->nCmdObjs = nCmdObjs;
	meth->cmdObjs = cmdObjs;
    }
done:
    Tcl_RestoreInterpState(interp, state);
    Tcl_Release(interp);
    Tcl_Release(uah);
    Tcl_Release(uai);
    return uaret;
}
 
................................................................................
    dsrc->nCmdObjs = 0;
    dsrc->cmdObjs = NULL;
    list = Tcl_NewListObj(nCmdObjs, cmdObjs);
    p = PrintNodeId(nodeId, &ds);
    Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(p, -1));
    Tcl_DStringFree(&ds);
    Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj("read", -1));

    dsrc->backRef = &dsrc;
    Tcl_IncrRefCount(list);
    ret = Tcl_GlobalEvalObj(interp, list);
    Tcl_DecrRefCount(list);
    if (dsrc == NULL) {
	/*
	 * Node was deleted, we're finished.
	 */
	goto done;
    }
    dsrc->backRef = NULL;

    if ((ret == TCL_BREAK) || (ret == TCL_OK)) {
	if (Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp),
				   &n, &elem) != TCL_OK) {
	    ret = TCL_ERROR;
	    goto error;
	}
	/*
................................................................................
	    Tcl_DecrRefCount(cmdObjs[i]);
	}
	ckfree((char *) cmdObjs);
    } else {
	dsrc->nCmdObjs = nCmdObjs;
	dsrc->cmdObjs = cmdObjs;
    }
done:
    Tcl_RestoreInterpState(interp, state);
    Tcl_Release(interp);
    Tcl_Release(uah);
    Tcl_Release(uai);
    return uaret;
}

................................................................................
	  int objc, Tcl_Obj * const objv[])
{
    UAI *uai = (UAI *) clientData;
    UAH *uah;
    UA_Client *cl = NULL;
    UA_Server *sv = NULL;
    Tcl_HashEntry *hPtr = NULL;
    int isNew, isServer = 0, port;
    char *pname, name[16];
    Tcl_DString ds;
    UA_ServerConfig *svcfg = NULL;
    static const char *typeNames[] = { "client", "server", NULL };

    if ((objc != 1) && (objc != 3) && (objc > 4)) {
	Tcl_WrongNumArgs(interp, 1, objv, "?type ?port? name?");
................................................................................
		Tcl_WrongNumArgs(interp, 1, objv, "server port name");
		return TCL_ERROR;
	    }
	    if (Tcl_GetIntFromObj(interp, objv[2], &port) != TCL_OK) {
		return TCL_ERROR;
	    }
	    hPtr = Tcl_CreateHashEntry(&uai->handles,
				       Tcl_GetString(objv[3]), &isNew);
	} else if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 1, objv, "client name");
	    return TCL_ERROR;
	} else {
	    hPtr = Tcl_CreateHashEntry(&uai->handles,
				       Tcl_GetString(objv[2]), &isNew);
	}
	if (!isNew) {
	    Tcl_SetResult(interp, "name already in use", TCL_STATIC);
	    ReportError(interp, uai, NULL, 0);
	    return TCL_ERROR;
	}
    }
    if (isServer) {
#if (UA_OPEN62541_VER_MAJOR >= 1)
................................................................................
    memset(&uah->typesArray, 0, sizeof(uah->typesArray));
#endif
    Tcl_InitHashTable(&uah->subs, TCL_ONE_WORD_KEYS);
    Tcl_InitHashTable(&uah->meths, TCL_STRING_KEYS);
    Tcl_InitHashTable(&uah->dsrcs, TCL_STRING_KEYS);
    if (hPtr == NULL) {
	sprintf(name, "ua%d", uai->idCount++);
	hPtr = Tcl_CreateHashEntry(&uai->handles, name, &isNew);
	if (!isNew) {
	    UAH *oldh = (UAH *) Tcl_GetHashValue(hPtr);

	    ReleaseTypes(oldh, 1);
	    ReleaseMethodsEtc(oldh, 1);
	    if (oldh->client != NULL) {
		DisconnectClient(oldh);
		if (oldh->client != NULL) {
................................................................................
    UAH *uah;
    Tcl_HashEntry *hPtr;
    UA_StatusCode uaret;
    UA_NodeId nodeid;
    const UA_DataType *type;
    UA_WriteValue wv;
    void *value;
    int attr, idx, n, j;
    UA_Int32 vr;

    if ((objc < 5) || (objc > 6)) {
	Tcl_WrongNumArgs(interp, 1, objv,
			 "handle nodeid ?attr? type value");
	return TCL_ERROR;
    }
    hPtr = Tcl_FindHashEntry(&uai->handles, Tcl_GetString(objv[1]));
................................................................................
    }
    if (ParseNodeId(interp, &nodeid, Tcl_GetString(objv[2])) == NULL) {
	return TCL_ERROR;
    }
    if (objc > 5) {
	if (Tcl_GetIndexFromObj(interp, objv[3], attrNames, "attr", 0,
				&attr) != TCL_OK) {
	    UA_NodeId_deleteMembers(&nodeid);
	    return TCL_ERROR;
	}
	/* UA_AttributeId is 1-based */
	attr += 1;
	idx = 4;
    } else {
	/* By default, write the "value" attribute. */
................................................................................
	attr = UA_ATTRIBUTEID_VALUE;
	idx = 3;
    }
    type = FindType(uai, uah, 0, Tcl_GetString(objv[idx]));
    if (type == NULL) {
	Tcl_SetResult(interp, "unsupported type", TCL_STATIC);
	goto error;
    }
    if (attr == UA_ATTRIBUTEID_VALUE) {
	if (uah->client != NULL) {
	    uaret = UA_Client_readValueRankAttribute(uah->client, nodeid, &vr);
	} else {
	    uaret = UA_Server_readValueRank(uah->server, nodeid, &vr);
	}
	if (uaret != UA_STATUSCODE_GOOD) {
	    Tcl_SetResult(interp, "cannot get value rank", TCL_STATIC);
	    ReportError(interp, uai, "ReadValueRank", uaret);
	    UA_NodeId_deleteMembers(&nodeid);
	    return TCL_ERROR;
	}
    } else {
	vr = -2;
    }
    idx++;
    if (vr >= 0) {
	/* An array, thus process a list. */
	void *vdata;
	Tcl_Obj **elem;

	if (Tcl_ListObjGetElements(interp, objv[idx], &n, &elem) != TCL_OK) {
	    UA_NodeId_deleteMembers(&nodeid);
	    return TCL_ERROR;
	}
	vdata = UA_Array_new(n, type);
	if (vdata == NULL) {
	    Tcl_SetResult(interp, "out of memory", TCL_STATIC);
	    UA_NodeId_deleteMembers(&nodeid);
	    return TCL_ERROR;
	}
	for (j = 0; j < n; j++) {
	    void *vv = EncodeFromObj(interp, uai, uah, type, NULL, elem[j], 0);

	    if (vv == NULL) {
		UA_Array_delete(vdata, n, type);
		goto error;
	    }
	    if (UA_copy(vv, (char *) vdata + j * type->memSize, type)
		!= UA_STATUSCODE_GOOD) {
		Tcl_SetResult(interp, "copy operation failed", TCL_STATIC);
		UA_Array_delete(vdata, n, type);
		goto error;
	    }
	}
	if (uah->client != NULL) {
	    const UA_DataType *vtype = &UA_TYPES[UA_TYPES_VARIANT];

	    value = UA_new(vtype);
	    if (value == NULL) {
		UA_Array_delete(vdata, n, type);
		Tcl_SetResult(interp, "out of memory", TCL_STATIC);
		UA_NodeId_deleteMembers(&nodeid);
		return TCL_ERROR;
	    }
	    UA_Variant_setArray(value, vdata, n, type);
	    type = vtype;
	} else {
	    value = vdata;
	}
    } else {
	value = EncodeFromObj(interp, uai, uah, type, NULL, objv[idx], 0);
	if (value == NULL) {
error:
	    ReportError(interp, uai, NULL, 0);
	    UA_NodeId_deleteMembers(&nodeid);
	    return TCL_ERROR;
	}
    }
    if (uah->client != NULL) {
	uaret = __UA_Client_writeAttribute(uah->client, &nodeid,
					   (UA_AttributeId) attr,
					   value, type);
    } else {
	UA_WriteValue_init(&wv);
	wv.nodeId = nodeid;
	wv.attributeId = (UA_AttributeId) attr;
	wv.value.hasValue = 1;
	if (vr >= 0) {
	    UA_Variant_setArray(&wv.value.value, value, n, type);
	} else {
	    UA_Variant_setScalar(&wv.value.value, value, type);
	}
	uaret = UA_Server_write(uah->server, &wv);
    }
    if (uaret != UA_STATUSCODE_GOOD) {
	ReportError(interp, uai, "WriteAttribute", uaret);
    }
    if (uah->client != NULL) {
	UA_NodeId_deleteMembers(&nodeid);
................................................................................
	    sub->prio = prio;
	    sub->enable = csrq.publishingEnabled;
	    Tcl_InitHashTable(&sub->monitors, TCL_ONE_WORD_KEYS);
	    csr = UA_Client_Subscriptions_create(uah->client, csrq, sub,
						 ChangeSubCB, ReleaseSubCB);
	    uaret = csr.responseHeader.serviceResult;
	    if (uaret == UA_STATUSCODE_GOOD) {
		int isNew;

		sub->subid = csr.subscriptionId;
		sub->interval = csr.revisedPublishingInterval;
		sub->ltCount = csr.revisedLifetimeCount;
		sub->kaCount = csr.revisedMaxKeepAliveCount;
		hPtr = Tcl_CreateHashEntry(&uah->subs,
					   INT2PTR(sub->subid), &isNew);
		if (!isNew) {
		    UAS *oldsub = (UAS *) Tcl_GetHashValue(hPtr);
		    Tcl_HashEntry *hPtr2;
		    Tcl_HashSearch search;

		    hPtr2 = Tcl_FirstHashEntry(&oldsub->monitors, &search);
		    while (hPtr2 != NULL) {
			UAM *mon = (UAM *) Tcl_GetHashValue(hPtr2);
................................................................................
	    if (mon->cmdObjs != NULL) {
		ckfree((char *) mon->cmdObjs);
	    }
	    ckfree((char *) mon);
	    ReportError(interp, uai, "CreateMonitor", uaret);
	    return TCL_ERROR;
	} else {
	    int isNew;

	    mon->interval = mon->interval;
	    mon->monid = mr.monitoredItemId;
	    hPtr = Tcl_CreateHashEntry(&sub->monitors,
				       INT2PTR(mon->monid), &isNew);
	    if (!isNew) {
		UAM *oldmon = (UAM *) Tcl_GetHashValue(hPtr);

		DeleteMonCB(uah->client, subid, sub, oldmon->monid, oldmon);
	    }
	    Tcl_SetHashValue(hPtr, mon);
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(mon->monid));
	}
................................................................................
    UA_StatusCode uaret;
    char *p;
    static const char *kindNames[] = {
	"Namespace",
	"Variable", "VariableType", "Object",
	"View", "ObjectType", "DataType", "ReferenceType",
	"Reference", "Method",
	"SimpleMethod",
	NULL
    };
    enum NodeKind {
	NK_Namespace,
	NK_Variable, NK_VariableType, NK_Object,
	NK_View, NK_ObjectType, NK_DataType, NK_ReferenceType,
	NK_Reference, NK_Method,
	NK_SimpleMethod
    };

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "handle kind ...");
	return TCL_ERROR;
    }
    hPtr = Tcl_FindHashEntry(&uai->handles, Tcl_GetString(objv[1]));
................................................................................
	/* At least preset the DisplayName attribute from browse name. */
	if (a->displayName.text.length == 0) {
	    UA_deleteMembers(&a->displayName.text,
			     &UA_TYPES[UA_TYPES_STRING]);
	    UA_String_copy(&qn.name, &a->displayName.text);
	}
	if (objc > 9) {
	    int nCmdObjs, i, isNew;
	    Tcl_Obj **cmdObjs;
	    UAQ *uaq;
	    static const UA_DataSource dsrc = {
		DataSourceRead, DataSourceWrite
	    };

	    if (Tcl_ListObjGetElements(interp, objv[9], &nCmdObjs, &cmdObjs)
................................................................................
	     */
	    uaq = (UAQ *) ckalloc(sizeof(UAQ));
	    memset(uaq, 0, sizeof(UAQ));
	    uaq->uah = NULL;
	    uaq->outType = NULL;
	    uaq->nCmdObjs = uaq->nSavedCmdObjs = 0;
	    uaq->cmdObjs = uaq->savedCmdObjs = NULL;
	    uaq->backRef = NULL;
	    uaret = UA_Server_addDataSourceVariableNode(uah->server,
			nodeid, parent, refid, qn, typeid,
			*((const UA_VariableAttributes *) a), dsrc, uaq,
			&retid);
	    if (uaret != UA_STATUSCODE_GOOD) {



		ckfree((char *) uaq);
	    } else {
		Tcl_DString ds;
		char *p;

		p = PrintNodeId(&retid, &ds);
		hPtr = Tcl_CreateHashEntry(&uah->dsrcs, p, &isNew);
		Tcl_DStringFree(&ds);
		if (!isNew) {
		    UAQ *oldq = (UAQ *) Tcl_GetHashValue(hPtr);

		    if (oldq->backRef != NULL) {
			*(oldq->backRef) = NULL;
			oldq->backRef = NULL;
		    }
		    if (oldq->cmdObjs != NULL) {
			for (i = 0; i < oldq->nSavedCmdObjs; i++) {
			    Tcl_DecrRefCount(oldq->savedCmdObjs[i]);
			}
			ckfree((char *) oldq->cmdObjs);
		    }
		    if ((oldq->savedCmdObjs != NULL) &&
			(oldq->savedCmdObjs != oldq->cmdObjs)) {
			for (i = 0; i < oldq->nSavedCmdObjs; i++) {
			    Tcl_DecrRefCount(oldq->savedCmdObjs[i]);
			}
			oldq->nSavedCmdObjs = 0;
			oldq->savedCmdObjs = NULL;
		    }
		    oldq->nCmdObjs = 0;
		    oldq->cmdObjs = NULL;
		    ckfree((char *) oldq);
		}
		Tcl_SetHashValue(hPtr, (ClientData) uaq);
		/*
		 * Initialize the callback information.
		 */
		uaq->uah = uah;
................................................................................
refError:
	UA_NodeId_deleteMembers(&nodeid);
	UA_NodeId_deleteMembers(&refid);
	UA_NodeId_deleteMembers(&destid);
	break;
    }

    case NK_Method:
    case NK_SimpleMethod: {
	UA_NodeId nodeid, parent, refid, retid;
	UA_QualifiedName qn;
	const UA_MethodAttributes *a0 = &UA_MethodAttributes_default;
	UA_MethodAttributes *a = NULL;
	int ian, oan, nCmdObjs;
	Tcl_Obj **cmdObjs;
	UA_Argument *ia = NULL, *oa = NULL;
................................................................................
	    goto methError;
	}
	p = Tcl_GetString(objv[5]);
	if ((*p != '\0') &&
	    (ParseRefTypeOrNodeId(interp, uai, &refid, p) == NULL)) {
	    goto methError;
	}
	oa = GetMethodArgs(interp, uah, kind == NK_SimpleMethod, objv[6], &oan);
	if (oan < 0) {
	    goto methError;
	}
	if (ParseQualifiedName(interp, &qn, Tcl_GetString(objv[7])) == NULL) {
	    goto methError;
	}
	ia = GetMethodArgs(interp, uah, kind == NK_SimpleMethod, objv[8], &ian);
	if (ian < 0) {
	    goto methError;
	}
	if (Tcl_ListObjGetElements(interp, objv[9], &nCmdObjs, &cmdObjs)
	    != TCL_OK) {
	    goto methError;
	}
................................................................................
	}
	/* At least preset the DisplayName attribute from browse name. */
	if (a->displayName.text.length == 0) {
	    UA_deleteMembers(&a->displayName.text,
			     &UA_TYPES[UA_TYPES_STRING]);
	    UA_String_copy(&qn.name, &a->displayName.text);
	}
	if ((kind == NK_SimpleMethod) && (oan > 1)) {
	    Tcl_SetResult(interp, "only one output argument supported",
			  TCL_STATIC);
	    goto methError;
	}
	if (oan > 0) {
	    oatype = NULL;
	    if ((oa[0].dataType.namespaceIndex != 0) &&
................................................................................
		Tcl_SetResult(interp, "unsupported output argument type",
			      TCL_STATIC);
		goto methError;
	    }
	}
	uaq = (UAQ *) ckalloc(sizeof(UAQ));
	memset(uaq, 0, sizeof(UAQ));
	uaq->uah = NULL;
	uaq->outType = NULL;
	uaq->nCmdObjs = uaq->nSavedCmdObjs = 0;
	uaq->cmdObjs = uaq->savedCmdObjs = NULL;
	uaq->backRef = NULL;
	uaret = UA_Server_addMethodNode(uah->server, nodeid, parent,
					refid, qn, *a, MethodInvoke, ian, ia,
					oan, oa, uaq, &retid);
	if (uaret != UA_STATUSCODE_GOOD) {
	    ReportError(interp, uai, "AddMethod", uaret);
	} else {
	    Tcl_DString ds;
	    char *p;
	    int i, isNew;

	    p = PrintNodeId(&retid, &ds);
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(p, -1));
	    hPtr = Tcl_CreateHashEntry(&uah->meths, p, &isNew);
	    if (!isNew) {
		UAQ *oldq = (UAQ *) Tcl_GetHashValue(hPtr);

		if (oldq->backRef != NULL) {
		    *(oldq->backRef) = NULL;
		    oldq->backRef = NULL;
		}
		if (oldq->cmdObjs != NULL) {
		    for (i = 0; i < oldq->nSavedCmdObjs; i++) {
			Tcl_DecrRefCount(oldq->savedCmdObjs[i]);
		    }
		    ckfree((char *) oldq->cmdObjs);
		}
		if ((oldq->savedCmdObjs != NULL) &&
		    (oldq->savedCmdObjs != oldq->cmdObjs)) {
		    for (i = 0; i < oldq->nSavedCmdObjs; i++) {
			Tcl_DecrRefCount(oldq->savedCmdObjs[i]);
		    }
		    oldq->nSavedCmdObjs = 0;
		    oldq->savedCmdObjs = NULL;
		}
		oldq->nCmdObjs = 0;
		oldq->cmdObjs = NULL;
		ckfree((char *) oldq);
	    }
	    Tcl_SetHashValue(hPtr, (ClientData) uaq);
	    uaq->uah = uah;
	    uaq->outType = oatype;
	    uaq->nCmdObjs = nCmdObjs;
	    uaq->cmdObjs = (Tcl_Obj **) ckalloc(nCmdObjs * sizeof(Tcl_Obj *));
................................................................................
	    UA_NodeId_deleteMembers(&destid);
	    return TCL_ERROR;
	}
	UA_NodeId_deleteMembers(&nodeid);
	UA_NodeId_deleteMembers(&refid);
	UA_NodeId_deleteMembers(&destid);
    } else {
	int i, withRefs = UA_FALSE;
	Tcl_DString ds;
	char *p;

	if (objc > 5) {
	    Tcl_WrongNumArgs(interp, 1, objv, "handle Node id ?withrefs?");
	    return TCL_ERROR;
	}
	UA_NodeId_init(&nodeid);
	if (ParseNodeId(interp, &nodeid, Tcl_GetString(objv[3])) == NULL) {
................................................................................
	uaret = UA_Server_deleteNode(uah->server, nodeid, withRefs);
	if (uaret != UA_STATUSCODE_GOOD) {
	    ReportError(interp, uai, "DeleteNode", uaret);
nodeError:
	    UA_NodeId_deleteMembers(&nodeid);
	    return TCL_ERROR;
	}

	/*
	 * Cleanup callbacks, if any.
	 */
	p = PrintNodeId(&nodeid, &ds);
	hPtr = Tcl_FindHashEntry(&uah->meths, p);
	if (hPtr != NULL) {
	    UAQ *meth = (UAQ *) Tcl_GetHashValue(hPtr);

	    if (meth->backRef != NULL) {
		*(meth->backRef) = NULL;
		meth->backRef = NULL;
	    }
	    Tcl_DeleteHashEntry(hPtr);
	    for (i = 0; i < meth->nCmdObjs; i++) {
		Tcl_DecrRefCount(meth->cmdObjs[i]);
	    }
	    if (meth->cmdObjs != NULL) {
		ckfree((char *) meth->cmdObjs);
	    }
	    ckfree((char *) meth);
	}
	hPtr = Tcl_FindHashEntry(&uah->dsrcs, p);
	if (hPtr != NULL) {
	    UAQ *dsrc = (UAQ *) Tcl_GetHashValue(hPtr);

	    if (dsrc->backRef != NULL) {
		*(dsrc->backRef) = NULL;
		dsrc->backRef = NULL;
	    }
	    Tcl_DeleteHashEntry(hPtr);
	    for (i = 0; i < dsrc->nCmdObjs; i++) {
		Tcl_DecrRefCount(dsrc->cmdObjs[i]);
	    }
	    if (dsrc->cmdObjs != NULL) {
		ckfree((char *) dsrc->cmdObjs);
	    }
	    ckfree((char *) dsrc);
	}
	Tcl_DStringFree(&ds);

	UA_NodeId_deleteMembers(&nodeid);
    }
    return TCL_OK;
}
 
/*
 *-------------------------------------------------------------------------
................................................................................

static int
MethodsObjCmd(ClientData clientData, Tcl_Interp *interp,
	      int objc, Tcl_Obj * const objv[])
{
    UAI *uai = (UAI *) clientData;
    UAH *uah;
    UAQ *uaq = NULL;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;

    char *p;


    if ((objc < 2) || (objc > 4)) {
	Tcl_WrongNumArgs(interp, 1, objv, "handle ?nodeid? ?outtype?");
	return TCL_ERROR;
    }
    hPtr = Tcl_FindHashEntry(&uai->handles, Tcl_GetString(objv[1]));
    if (hPtr == NULL) {
	Tcl_SetResult(interp, "handle not found", TCL_STATIC);
	ReportError(interp, uai, NULL, 0);
	return TCL_ERROR;
    }
    uah = (UAH *) Tcl_GetHashValue(hPtr);
    hPtr = Tcl_FirstHashEntry(&uah->meths, &search);
    if (objc == 2) {
	Tcl_Obj *list = Tcl_NewListObj(0, NULL);
	Tcl_DString ds;
	char *p;

	while (hPtr != NULL) {
	    uaq = (UAQ *) Tcl_GetHashValue(hPtr);

	    Tcl_ListObjAppendElement(NULL, list,
		Tcl_NewStringObj(Tcl_GetHashKey(&uah->meths, hPtr), -1));
	    if (uaq->outType == NULL) {
		Tcl_ListObjAppendElement(NULL, list, Tcl_NewObj());
	    } else if (uaq->outType->typeId.namespaceIndex == 0) {
		Tcl_ListObjAppendElement(NULL, list,
		    Tcl_NewStringObj(uaq->outType->typeName, -1));
	    } else {
		p = PrintNodeId(&uaq->outType->typeId, &ds);
		Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(p, -1));
		Tcl_DStringFree(&ds);
	    }
	    Tcl_ListObjAppendElement(NULL, list,
		Tcl_NewListObj(uaq->nSavedCmdObjs, uaq->savedCmdObjs));
	    /* TBD: add input argument list */
	    hPtr = Tcl_NextHashEntry(&search);
	}
	Tcl_SetObjResult(interp, list);
	return TCL_OK;
    }
    p = Tcl_GetString(objv[2]);
    while (hPtr != NULL) {
	uaq = (UAQ *) Tcl_GetHashValue(hPtr);
	if (strcmp(p, Tcl_GetHashKey(&uah->meths, hPtr)) == 0) {
	    break;
	}
	hPtr = Tcl_NextHashEntry(&search);
    }
    if (hPtr == NULL) {
	Tcl_SetResult(interp, "method node not found", TCL_STATIC);
	ReportError(interp, uai, NULL, 0);
	return TCL_ERROR;
    }
    if (objc == 3) {
	Tcl_Obj *objs[3];
	Tcl_DString ds;
	char *p;

	objs[0] = Tcl_NewStringObj(Tcl_GetHashKey(&uah->meths, hPtr), -1);
	if (uaq->outType == NULL) {
	    objs[1] = Tcl_NewObj();
	} else if (uaq->outType->typeId.namespaceIndex == 0) {
	    objs[1] = Tcl_NewStringObj(uaq->outType->typeName, -1);
	} else {
	    p = PrintNodeId(&uaq->outType->typeId, &ds);
	    objs[1] = Tcl_NewStringObj(p, -1);
	    Tcl_DStringFree(&ds);
	}
	objs[2] = Tcl_NewListObj(uaq->nSavedCmdObjs, uaq->savedCmdObjs);
	/* TBD: add input argument list */
	Tcl_SetObjResult(interp, Tcl_NewListObj(3, objs));
    } else {
	p = Tcl_GetString(objv[3]);
	if (*p == '\0') {
	    uaq->outType = NULL;
	} else {
	    const UA_DataType *type = FindType(uai, uah, 0, p);

	    if (type == NULL) {
		Tcl_SetResult(interp, "unsupported type", TCL_STATIC);
		ReportError(interp, uai, NULL, 0);
		return TCL_ERROR;
	    }
	    uaq->outType = type;
	}
    }
    return TCL_OK;
}
 
/*
 *-------------------------------------------------------------------------
 *
 * DataSourcesObjCmd --
................................................................................
	} else {
	    size_t typesSize = uah->typesSize;
	    UA_DataType *types = uah->types;

	    uah->typesSize = 0;
	    uah->types = NULL;
	    ReleaseTypes(uah, 1);

#if (UA_OPEN62541_VER_MAJOR < 1)
	    if (uah->client != NULL) {
		UA_Client_setCustomDataTypes(uah->client, types, typesSize);
	    } else {
		UA_Server_setCustomDataTypes(uah->server, types, typesSize);
	    }
#else
................................................................................
	    return TCL_ERROR;
	}
	if (ParseNodeId(interp, &encid, Tcl_GetString(objv[5])) == NULL) {
	    return TCL_ERROR;
	}
	/* Validate member types. */
	for (i = 6; i < objc; i += 2) {
	    char *p;

	    p = Tcl_GetString(objv[i]);
	    if (*p == '*') {
		++p;
	    }
	    type = FindType(uai, uah, 1, p);
	    if (type == NULL) {
		Tcl_SetResult(interp, "unknown type in struct", TCL_STATIC);
		ReportError(interp, uai, NULL, 0);
		return TCL_ERROR;
	    }
	}
	max = nodeid.identifier.numeric;
	if (max < encid.identifier.numeric) {
	    max = encid.identifier.numeric;
	}
	max += 64;
	if (uah->types == NULL) {
................................................................................
	uah->types[i].pointerFree = 1;
	uah->types[i].overlayable = 0;
	uah->types[i].binaryEncodingId = encid.identifier.numeric;
	j = sizeof(UA_DataTypeMember) * uah->types[i].membersSize;
	uah->types[i].members = ckalloc(j);
	memset(uah->types[i].members, 0, j);
	for (j = 6; j < objc; j += 2) {
	    int k, isZero, isArray = 0;
	    char *p;

	    p = Tcl_GetString(objv[j]);
	    if (*p == '*') {
		++p;
		isArray = 1;
	    }
	    type = FindType(uai, uah, 1, p);
	    if ((type >= &UA_TYPES[0]) && (type < &UA_TYPES[UA_TYPES_COUNT])) {
		isZero = 1;
	    } else {
		isZero = 0;
	    }
	    k = (j - 6) / 2;
	    obj = GetSymObj(uai, Tcl_GetString(objv[j + 1]),
................................................................................
	    uah->types[i].members[k].memberTypeIndex = type->typeIndex;
	    uah->types[i].members[k].padding =
		sizeof(double) - (uah->types[i].memSize % sizeof(double));
	    if (uah->types[i].members[k].padding == sizeof(double)) {
		uah->types[i].members[k].padding = 0;
	    }
	    uah->types[i].members[k].namespaceZero = isZero;
	    uah->types[i].members[k].isArray = isArray;
	    if (!type->pointerFree) {
		uah->types[i].pointerFree = 0;
	    }
	    uah->types[i].memSize +=
		type->memSize + uah->types[i].members[k].padding;
	}
	break;
................................................................................
 *-------------------------------------------------------------------------
 */

int DLLEXPORT
Topcua_Init(Tcl_Interp *interp)
{
    UAI *uai;
    int i, skip, isNew;
    Tcl_HashEntry *hPtr;
    Tcl_Namespace *nsPtr;
    Tcl_Obj *cmdList;
    Tcl_Command cmd;

    /*
     * UA data types for which a mapping is provided.
     *
     * CAUTION: must be adjusted with EncodeFromObj()/DecodeToObj().
     */
    static const int types[] = {
	UA_TYPES_BOOLEAN, UA_TYPES_SBYTE, UA_TYPES_BYTE,
	UA_TYPES_INT16, UA_TYPES_UINT16, UA_TYPES_INT32,
	UA_TYPES_UINT32, UA_TYPES_INT64, UA_TYPES_UINT64,
	UA_TYPES_FLOAT, UA_TYPES_DOUBLE, UA_TYPES_STRING,
	UA_TYPES_DATETIME, UA_TYPES_GUID, UA_TYPES_BYTESTRING,
	UA_TYPES_XMLELEMENT, UA_TYPES_NODEID, UA_TYPES_EXPANDEDNODEID,
	UA_TYPES_STATUSCODE, UA_TYPES_QUALIFIEDNAME,
	UA_TYPES_LOCALIZEDTEXT, UA_TYPES_EXTENSIONOBJECT,
	UA_TYPES_DATAVALUE, UA_TYPES_VARIANT, UA_TYPES_DIAGNOSTICINFO,
#if (UA_OPEN62541_VER_MAJOR >= 1)
	UA_TYPES_UTCTIME, UA_TYPES_LOCALEID, UA_TYPES_DURATION,
#endif
    };

#if (UA_OPEN62541_VER_MAJOR < 1)
    /*
     * Aliased types as found with
     *   grep "#define.*UA_TYPES_.*UA_TYPES_" open62541/open62541.h
     */
    static const struct {
	const char *name;
	int type;
    } typeAliases[] = {
	{ "UtcTime", UA_TYPES_DATETIME },
	{ "LocaleId", UA_TYPES_STRING },
	{ "Duration", UA_TYPES_DOUBLE }
    };
#endif

    /*
     * Namespace commands to create.
     */
    static const struct {
	const char *name;
	Tcl_ObjCmdProc *proc;
................................................................................

    /*
     * Basic (scalar) types we can map.
     */
    Tcl_InitHashTable(&uai->types, TCL_STRING_KEYS);
    for (i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
	hPtr = Tcl_CreateHashEntry(&uai->types,
				   UA_TYPES[types[i]].typeName, &isNew);
	Tcl_SetHashValue(hPtr, &UA_TYPES[i]);
    }
#if (UA_OPEN62541_VER_MAJOR < 1)
    for (i = 0; i < sizeof(typeAliases) / sizeof(typeAliases[0]); i++) {
	hPtr = Tcl_CreateHashEntry(&uai->types,
				   typeAliases[i].name, &isNew);
	Tcl_SetHashValue(hPtr, &UA_TYPES[typeAliases[i].type]);
    }
#endif

    /*
     * Reference types.
     */
    Tcl_InitHashTable(&uai->refTypes, TCL_STRING_KEYS);
    for (i = 0; i < sizeof(refTypes) / sizeof(refTypes[0]); i++) {
	hPtr = Tcl_CreateHashEntry(&uai->refTypes, refTypes[i].name, &isNew);
	Tcl_SetHashValue(hPtr, INT2PTR(refTypes[i].ns0id));
    }

    /*
     * Map of symbols used e.g. in data represented as dict.
     */
    Tcl_InitHashTable(&uai->syms, TCL_STRING_KEYS);