063b323948
(Boeing r1871)
3287 lines
100 KiB
Tcl
Executable file
3287 lines
100 KiB
Tcl
Executable file
#
|
|
# CORE API
|
|
# Copyright 2005-2013 the Boeing Company.
|
|
# See the LICENSE file included in this distribution.
|
|
#
|
|
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
|
#
|
|
|
|
# version of the API document that is used
|
|
set CORE_API_VERSION 1.23
|
|
|
|
set DEFAULT_API_PORT 4038
|
|
set g_api_exec_num 100; # starting execution number
|
|
|
|
# set scale for X/Y coordinate translation
|
|
set XSCALE 1.0
|
|
set YSCALE 1.0
|
|
set XOFFSET 0
|
|
set YOFFSET 0
|
|
|
|
# current session; 0 is a new session
|
|
set g_current_session 0
|
|
set g_session_dialog_hint 1
|
|
|
|
# this is an array of lists, with one array entry for each widget or callback,
|
|
# and the entry is a list of execution numbers (for matching replies with
|
|
# requests)
|
|
array set g_execRequests { shell "" observer "" }
|
|
|
|
# for a simulator, uncomment this line or cut/paste into debugger:
|
|
# set XSCALE 4.0; set YSCALE 4.0; set XOFFSET 1800; set YOFFSET 300
|
|
|
|
array set nodetypes { 0 def 1 phys 2 xen 3 tbd 4 lanswitch 5 hub \
|
|
6 wlan 7 rj45 8 tunnel 9 ktunnel 10 emane }
|
|
|
|
array set regtypes { wl 1 mob 2 util 3 exec 4 gui 5 emul 6 }
|
|
array set regntypes { 1 wl 2 mob 3 util 4 exec 5 gui 6 emul 7 relay 10 session }
|
|
array set regtxttypes { wl "Wireless Module" mob "Mobility Module" \
|
|
util "Utility Module" exec "Execution Server" \
|
|
gui "Graphical User Interface" emul "Emulation Server" \
|
|
relay "Relay" }
|
|
set DEFAULT_GUI_REG "gui core_2d_gui"
|
|
array set eventtypes { definition_state 1 configuration_state 2 \
|
|
instantiation_state 3 runtime_state 4 \
|
|
datacollect_state 5 shutdown_state 6 \
|
|
event_start 7 event_stop 8 event_pause 9 \
|
|
event_restart 10 file_open 11 file_save 12 \
|
|
event_scheduled 31 }
|
|
|
|
set CORE_STATES \
|
|
"NONE DEFINITION CONFIGURATION INSTANTIATION RUNTIME DATACOLLECT SHUTDOWN"
|
|
|
|
set EXCEPTION_LEVELS \
|
|
"NONE FATAL ERROR WARNING NOTICE"
|
|
|
|
# Event handler invoked for each message received by peer
|
|
proc receiveMessage { channel } {
|
|
global curcanvas showAPI
|
|
set prmsg $showAPI
|
|
set type 0
|
|
set flags 0
|
|
set len 0
|
|
set seq 0
|
|
|
|
#puts "API receive data."
|
|
# disable the fileevent here, then reinstall the handler at the end
|
|
fileevent $channel readable ""
|
|
# channel closed
|
|
if { [eof $channel] } {
|
|
resetChannel channel 1
|
|
return
|
|
}
|
|
|
|
#
|
|
# read first four bytes of message header
|
|
set more_data 1
|
|
while { $more_data == 1 } {
|
|
if { [catch { set bytes [read $channel 4] } e] } {
|
|
# in tcl8.6 this occurs during shutdown
|
|
#puts "channel closed: $e"
|
|
break;
|
|
}
|
|
if { [fblocked $channel] == 1} {
|
|
# 4 bytes not available yet
|
|
break;
|
|
} elseif { [eof $channel] } {
|
|
resetChannel channel 1
|
|
break;
|
|
} elseif { [string bytelength $bytes] == 0 } {
|
|
# zero bytes read - parseMessageHeader would fail
|
|
break;
|
|
}
|
|
# parse type/flags/length
|
|
if { [parseMessageHeader $bytes type flags len] < 0 } {
|
|
# Message header error
|
|
break;
|
|
}
|
|
# read message data of specified length
|
|
set bytes [read $channel $len]
|
|
#if { $prmsg== 1} {
|
|
# puts "read $len bytes (type=$type, flags=$flags, len=$len)..."
|
|
#}
|
|
# handle each message type
|
|
switch -exact -- "$type" {
|
|
1 { parseNodeMessage $bytes $len $flags }
|
|
2 { parseLinkMessage $bytes $len $flags }
|
|
3 { parseExecMessage $bytes $len $flags $channel }
|
|
4 { parseRegMessage $bytes $len $flags $channel }
|
|
5 { parseConfMessage $bytes $len $flags $channel }
|
|
6 { parseFileMessage $bytes $len $flags $channel }
|
|
8 { parseEventMessage $bytes $len $flags $channel }
|
|
9 { parseSessionMessage $bytes $len $flags $channel }
|
|
10 { parseExceptionMessage $bytes $len $flags $channel;
|
|
#7 { parseIfaceMessage $bytes $len $flags $channel }
|
|
#
|
|
}
|
|
default { puts "Unknown Message = $type" }
|
|
}
|
|
# end switch
|
|
}
|
|
# end while
|
|
|
|
# update the canvas
|
|
catch {
|
|
# this messes up widgets
|
|
#raiseAll .c
|
|
.c config -cursor left_ptr ;# otherwise we have hourglass/pirate
|
|
update
|
|
}
|
|
|
|
if {$channel != -1 } {
|
|
resetChannel channel 0
|
|
}
|
|
}
|
|
|
|
#
|
|
# Open an API socket to the specified server:port, prompt user for retry
|
|
# if specified; set the readable file event and parameters;
|
|
# returns the channel name or -1 on error.
|
|
#
|
|
proc openAPIChannel { server port retry } {
|
|
# use default values (localhost:4038) when none specified
|
|
if { $server == "" || $server == 0 } {
|
|
set server "localhost"
|
|
}
|
|
if { $port == 0 } {
|
|
global DEFAULT_API_PORT
|
|
set port $DEFAULT_API_PORT
|
|
}
|
|
|
|
# loop when retry is true
|
|
set s -1
|
|
while { $s < 0 } {
|
|
# TODO: fix this to remove lengthy timeout periods...
|
|
# (need to convert all channel I/O to use async channel)
|
|
# vwait doesn't work here, blocks on socket call
|
|
#puts "Connecting to $server:$port..."; # verbose
|
|
set svcstart [getServiceStartString]
|
|
set e "This feature requires a connection to the CORE daemon.\n"
|
|
set e "$e\nFailed to connect to $server:$port!\n"
|
|
set e "$e\nHave you started the CORE daemon with"
|
|
set e "$e '$svcstart'?"
|
|
if { [catch {set s [socket $server $port]} ex] } {
|
|
puts "\n$e\n (Error: $ex)"
|
|
set s -1
|
|
if { ! $retry } { return $s; }; # error, don't retry
|
|
}
|
|
if { $s > 0 } { puts "connected." }; # verbose
|
|
if { $retry } {; # prompt user with retry dialog
|
|
if { $s < 0 } {
|
|
set choice [tk_dialog .connect "Error" $e \
|
|
error 0 Retry "Start daemon..." Cancel]
|
|
if { $choice == 2 } { return $s } ;# cancel
|
|
if { $choice == 1 } {
|
|
set sudocmd "gksudo"
|
|
set cmd "core-daemon -d"
|
|
if { [catch {exec $sudocmd $cmd & } e] } {
|
|
puts "Error running '$sudocmd $cmd'!"
|
|
}
|
|
after 300 ;# allow time for daemon to start
|
|
}
|
|
# fall through for retry...
|
|
}
|
|
}
|
|
}; # end while
|
|
|
|
# now we have a valid socket, set up encoding and receive event
|
|
fconfigure $s -blocking 0 -encoding binary -translation { binary binary } \
|
|
-buffering full -buffersize 4096
|
|
fileevent $s readable [list receiveMessage $s]
|
|
return $s
|
|
}
|
|
|
|
#
|
|
# Reinstall the receiveMessage event handler
|
|
#
|
|
proc resetChannel { channel_ptr close } {
|
|
upvar 1 $channel_ptr channel
|
|
if {$close == 1} {
|
|
close $channel
|
|
pluginChannelClosed $channel
|
|
set $channel -1
|
|
}
|
|
if { [catch { fileevent $channel readable \
|
|
[list receiveMessage $channel] } ] } {
|
|
# may print error here
|
|
}
|
|
}
|
|
|
|
#
|
|
# Catch errors when flushing sockets
|
|
#
|
|
proc flushChannel { channel_ptr msg } {
|
|
upvar 1 $channel_ptr channel
|
|
if { [catch { flush $channel } err] } {
|
|
puts "*** $msg: $err"
|
|
set channel -1
|
|
return -1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# CORE message header
|
|
#
|
|
proc parseMessageHeader { bytes type flags len } {
|
|
# variables are passed by reference
|
|
upvar 1 $type mytype
|
|
upvar 1 $flags myflags
|
|
upvar 1 $len mylen
|
|
|
|
#
|
|
# read the four-byte message header
|
|
#
|
|
if { [binary scan $bytes ccS mytype myflags mylen] != 3 } {
|
|
puts "*** warning: message header error"
|
|
return -1
|
|
} else {
|
|
set mytype [expr {$mytype & 0xFF}]; # convert signed to unsigned
|
|
set myflags [expr {$myflags & 0xFF}]
|
|
if { $mylen == 0 } {
|
|
puts "*** warning: zero length message header!"
|
|
# empty the channel
|
|
#set bytes [read $channel]
|
|
return -1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
|
|
#
|
|
# CORE API Node message TLVs
|
|
#
|
|
proc parseNodeMessage { data len flags } {
|
|
global node_list curcanvas c router eid showAPI nodetypes CORE_DATA_DIR
|
|
global XSCALE YSCALE XOFFSET YOFFSET deployCfgAPI_lock
|
|
#puts "Parsing node message of length=$len, flags=$flags"
|
|
set prmsg $showAPI
|
|
set current 0
|
|
|
|
array set typenames { 1 num 2 type 3 name 4 ipv4_addr 5 mac_addr \
|
|
6 ipv6_addr 7 model 8 emulsrv 10 session \
|
|
32 xpos 33 ypos 34 canv \
|
|
35 emuid 36 netid 37 services \
|
|
48 lat 49 long 50 alt \
|
|
66 icon 80 opaque }
|
|
array set typesizes { num 4 type 4 name -1 ipv4_addr 4 ipv6_addr 16 \
|
|
mac_addr 8 model -1 emulsrv -1 session -1 \
|
|
xpos 2 ypos 2 canv 2 emuid 4 \
|
|
netid 4 services -1 lat 4 long 4 alt 4 \
|
|
icon -1 opaque -1 }
|
|
array set vals { num 0 type 0 name "" ipv4_addr -1 ipv6_addr -1 \
|
|
mac_addr -1 model "" emulsrv "" session "" \
|
|
xpos 0 ypos 0 canv "" \
|
|
emuid -1 netid -1 services "" \
|
|
lat 0 long 0 alt 0 \
|
|
icon "" opaque "" }
|
|
|
|
if { $prmsg==1 } { puts -nonewline "NODE(flags=$flags," }
|
|
|
|
#
|
|
# TLV parsing
|
|
#
|
|
while { $current < $len } {
|
|
# TLV header
|
|
if { [binary scan $data @${current}cc type length] != 2 } {
|
|
puts "TLV header error"
|
|
break
|
|
}
|
|
set length [expr {$length & 0xFF}]; # convert signed to unsigned
|
|
if { $length == 0 } {; # prevent endless looping
|
|
if { $type == 0 } { puts -nonewline "(extra padding)"; break
|
|
} else { puts "Found zero-length TLV for type=$type, dropping.";
|
|
break }
|
|
}
|
|
set pad [pad_32bit $length]
|
|
# verbose debugging
|
|
#puts "tlv type=$type length=$length pad=$pad current=$current"
|
|
incr current 2
|
|
|
|
if {![info exists typenames($type)] } { ;# unknown TLV type
|
|
if { $prmsg } { puts -nonewline "unknown=$type," }
|
|
incr current $length
|
|
continue
|
|
}
|
|
set typename $typenames($type)
|
|
set size $typesizes($typename)
|
|
# 32-bit and 64-bit vals pre-padded
|
|
if { $size == 4 || $size == 8 } { incr current $pad }
|
|
# read TLV data depending on size
|
|
switch -exact -- "$size" {
|
|
2 { binary scan $data @${current}S vals($typename) }
|
|
4 { binary scan $data @${current}I vals($typename) }
|
|
8 { binary scan $data @${current}W vals($typename) }
|
|
16 { binary scan $data @${current}c16 vals($typename) }
|
|
-1 { binary scan $data @${current}a${length} vals($typename) }
|
|
}
|
|
if { $size == -1 } { incr current $pad } ;# string vals post-padded
|
|
if { $type == 6 } { incr current $pad } ;# 128-bit vals post-padded
|
|
incr current $length
|
|
# special handling of data here
|
|
switch -exact -- "$typename" {
|
|
ipv4_addr { array set vals [list $typename \
|
|
[ipv4ToString $vals($typename)] ] }
|
|
mac_addr { array set vals [list $typename \
|
|
[macToString $vals($typename)] ] }
|
|
ipv6_addr { array set vals [list $typename \
|
|
[ipv6ToString $vals($typename)] ] }
|
|
xpos { array set vals [list $typename \
|
|
[expr { ($vals($typename) * $XSCALE) - $XOFFSET }] ] }
|
|
ypos { array set vals [list $typename \
|
|
[expr { ($vals($typename) * $YSCALE) - $YOFFSET }] ] }
|
|
}
|
|
if { $prmsg } { puts -nonewline "$typename=$vals($typename)," }
|
|
}
|
|
|
|
if { $prmsg } { puts ") "}
|
|
|
|
#
|
|
# Execution
|
|
#
|
|
# TODO: enforce message parameters here
|
|
if { ![info exists nodetypes($vals(type))] } {
|
|
puts "NODE: invalid node type ($vals(type)), dropping"; return
|
|
}
|
|
set node "n$vals(num)"
|
|
set node_id "$eid\_$node"
|
|
if { [lsearch $node_list $node] == -1 } {; # check for node existance
|
|
set exists false
|
|
} else {
|
|
set exists true
|
|
}
|
|
|
|
if { $vals(name) == "" } {; # make sure there is a node name
|
|
set name $node
|
|
if { $exists } { set name [getNodeName $node] }
|
|
array set vals [list name $name]
|
|
}
|
|
if { $exists } {
|
|
if { $flags == 1 } {
|
|
puts "Node add msg but node ($node) already exists, dropping."
|
|
return
|
|
}
|
|
} elseif { $flags != 1 } {
|
|
puts -nonewline "Node modify/delete message but node ($node) does "
|
|
puts "not exist dropping."
|
|
return
|
|
}
|
|
if { $vals(icon) != "" } {
|
|
set icon $vals(icon)
|
|
if { [file pathtype $icon] == "relative" } {
|
|
set icon "$CORE_DATA_DIR/icons/normal/$icon"
|
|
}
|
|
if { ![file exists $icon ] } {
|
|
puts "Node icon '$vals(icon)' does not exist."
|
|
array set vals [list icon ""]
|
|
} else {
|
|
array set vals [list icon $icon]
|
|
}
|
|
}
|
|
global $node
|
|
|
|
set wlans_needing_update { }
|
|
if { $vals(emuid) != -1 } {
|
|
# For Linux (FreeBSD populates ngnodeidmap in l3node.instantiate/
|
|
# buildInterface when the netgraph ID is known)
|
|
# populate ngnodeidmap for later use with wireless; it is treated as
|
|
# a hex value string (without the leading "0x")
|
|
global ngnodeidmap
|
|
foreach wlan [findWlanNodes $node] {
|
|
if { ![info exists ngnodeidmap($eid\_$wlan)] } {
|
|
set netid [string range $wlan 1 end]
|
|
set emulation_type [lindex [getEmulPlugin $node] 1]
|
|
# TODO: verify that this incr 1000 is for OpenVZ
|
|
if { $emulation_type == "openvz" } { incr netid 1000 }
|
|
set ngnodeidmap($eid\_$wlan) [format "%x" $netid]
|
|
}
|
|
if { ![info exists ngnodeidmap($eid\_$wlan-$node)] } {
|
|
set ngnodeidmap($eid\_$wlan-$node) [format "%x" $vals(emuid)]
|
|
lappend wlans_needing_update $wlan
|
|
}
|
|
} ;# end foreach wlan
|
|
}
|
|
|
|
# local flags: informational message that node was added or deleted
|
|
if {[expr {$flags & 0x8}]} {
|
|
if { ![info exists c] } { return }
|
|
if {[expr {$flags & 0x1}] } { ;# add flag
|
|
nodeHighlights $c $node on green
|
|
after 3000 "nodeHighlights .c $node off green"
|
|
} elseif {[expr {$flags & 0x2}] } { ;# delete flag
|
|
nodeHighlights $c $node on black
|
|
after 3000 "nodeHighlights .c $node off black"
|
|
}
|
|
# note: we may want to save other data passed in this message here
|
|
# rather than just returning...
|
|
return
|
|
}
|
|
# now we have all the information about this node
|
|
switch -exact -- "$flags" {
|
|
0 { apiNodeModify $node vals }
|
|
1 { apiNodeCreate $node vals }
|
|
2 { apiNodeDelete $node }
|
|
default { puts "NODE: unsupported flags ($flags)"; return }
|
|
}
|
|
}
|
|
|
|
#
|
|
# modify a node
|
|
#
|
|
proc apiNodeModify { node vals_ref } {
|
|
global c eid zoom
|
|
upvar $vals_ref vals
|
|
if { ![info exists c] } { return } ;# batch mode
|
|
if { $vals(icon) != "" } {
|
|
setCustomImage $node $vals(icon)
|
|
.c delete withtag "node && $node"
|
|
.c delete withtag "nodelabel && $node"
|
|
drawNode .c $node
|
|
}
|
|
# move the node and its links
|
|
if {$vals(xpos) != 0 && $vals(ypos) != 0} {
|
|
moveNodeAbs $c $node [expr {$zoom * $vals(xpos)}] \
|
|
[expr {$zoom * $vals(ypos)}]
|
|
}
|
|
if { $vals(name) != "" } {
|
|
setNodeName $node $vals(name)
|
|
}
|
|
if { $vals(services) != "" } {
|
|
set services [split $vals(services) |]
|
|
setNodeServices $node $services
|
|
}
|
|
# TODO: handle other optional on-screen data
|
|
# lat, long, alt, heading, platform type, platform id
|
|
}
|
|
|
|
#
|
|
# add a node
|
|
#
|
|
proc apiNodeCreate { node vals_ref } {
|
|
global $node nodetypes node_list canvas_list curcanvas eid
|
|
upvar $vals_ref vals
|
|
|
|
# create GUI object
|
|
set nodetype $nodetypes($vals(type))
|
|
set nodename $vals(name)
|
|
if { $nodetype == "emane" } { set nodetype "wlan" } ;# special case - EMANE
|
|
if { $nodetype == "def" || $nodetype == "xen" } { set nodetype "router" }
|
|
newNode [list $nodetype $node] ;# use node number supplied from API message
|
|
setNodeName $node $nodename
|
|
if { $vals(canv) == "" } {
|
|
setNodeCanvas $node $curcanvas
|
|
} else {
|
|
set canv $vals(canv)
|
|
if { ![string is integer $canv] || $canv < 0 || $canv > 100} {
|
|
puts "warning: invalid canvas '$canv' in Node message!"
|
|
return
|
|
}
|
|
set canv "c$canv"
|
|
if { [lsearch $canvas_list $canv] < 0 && $canv == "c0" } {
|
|
# special case -- support old imn files with Canvas0
|
|
global $canv
|
|
lappend canvas_list $canv
|
|
set $canv {}
|
|
setCanvasName $canv "Canvas0"
|
|
set curcanvas $canv
|
|
switchCanvas none
|
|
} else {
|
|
while { [lsearch $canvas_list $canv] < 0 } {
|
|
set canvnew [newCanvas ""]
|
|
switchCanvas none ;# redraw canvas tabs
|
|
}
|
|
}
|
|
setNodeCanvas $node $canv
|
|
}
|
|
setNodeCoords $node "$vals(xpos) $vals(ypos)"
|
|
lassign [getDefaultLabelOffsets [nodeType $node]] dx dy
|
|
setNodeLabelCoords $node "[expr $vals(xpos) + $dx] [expr $vals(ypos) + $dy]"
|
|
setNodeLocation $node $vals(emulsrv)
|
|
if { $vals(icon) != "" } {
|
|
setCustomImage $node $vals(icon)
|
|
}
|
|
drawNode .c $node
|
|
|
|
set model $vals(model)
|
|
if { $model != "" && $vals(type) < 4} {
|
|
# set model only for (0 def 1 phys 2 xen 3 tbd) 4 lanswitch
|
|
setNodeModel $node $model
|
|
if { [lsearch -exact [getNodeTypeNames] $model] == -1 } {
|
|
puts "warning: unknown node type '$model' in Node message!"
|
|
}
|
|
}
|
|
if { $vals(services) != "" } {
|
|
set services [split $vals(services) |]
|
|
setNodeServices $node $services
|
|
}
|
|
|
|
if { $vals(type) == 7 } { ;# RJ45 node - used later to control linking
|
|
netconfInsertSection $node [list model $vals(model)]
|
|
} elseif { $vals(type) == 10 } { ;# EMANE node
|
|
set section [list mobmodel coreapi ""]
|
|
netconfInsertSection $node $section
|
|
#set sock [lindex [getEmulPlugin $node] 2]
|
|
#sendConfRequestMessage $sock $node "all" 0x1 -1 ""
|
|
} elseif { $vals(type) == 6 } { ;# WLAN node
|
|
if { $vals(opaque) != "" } {
|
|
# treat opaque as a list to accomodate other data
|
|
set i [lsearch $vals(opaque) "range=*"]
|
|
if { $i != -1 } {
|
|
set range [lindex $vals(opaque) $i]
|
|
setNodeRange $node [lindex [split $range =] 1]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# delete a node
|
|
#
|
|
proc apiNodeDelete { node } {
|
|
removeGUINode $node
|
|
}
|
|
|
|
#
|
|
# CORE API Link message TLVs
|
|
#
|
|
proc parseLinkMessage { data len flags } {
|
|
global router def_router_model eid
|
|
global link_list node_list ngnodeidmap ngnodeidrmap showAPI execMode
|
|
set prmsg $showAPI
|
|
set current 0
|
|
set c .c
|
|
#puts "Parsing link message of length=$len, flags=$flags"
|
|
|
|
array set typenames { 1 node1num 2 node2num 3 delay 4 bw 5 per \
|
|
6 dup 7 jitter 8 mer 9 burst 10 session \
|
|
16 mburst 32 ltype 33 guiattr 34 uni \
|
|
35 emuid1 36 netid 37 key \
|
|
48 if1num 49 if1ipv4 50 if1ipv4mask 51 if1mac \
|
|
52 if1ipv6 53 if1ipv6mask \
|
|
54 if2num 55 if2ipv4 56 if2ipv4mask 57 if2mac \
|
|
64 if2ipv6 65 if2ipv6mask }
|
|
array set typesizes { node1num 4 node2num 4 delay 8 bw 8 per -1 \
|
|
dup -1 jitter 8 mer 2 burst 2 session -1 \
|
|
mburst 2 ltype 4 guiattr -1 uni 2 \
|
|
emuid1 4 netid 4 key 4 \
|
|
if1num 2 if1ipv4 4 if1ipv4mask 2 if1mac 8 \
|
|
if1ipv6 16 if1ipv6mask 2 \
|
|
if2num 2 if2ipv4 4 if2ipv4mask 2 if2mac 8 \
|
|
if2ipv6 16 if2ipv6mask 2 }
|
|
array set vals { node1num -1 node2num -1 delay 0 bw 0 per "" \
|
|
dup "" jitter 0 mer 0 burst 0 session "" \
|
|
mburst 0 ltype 0 guiattr "" uni 0 \
|
|
emuid1 -1 netid -1 key -1 \
|
|
if1num -1 if1ipv4 -1 if1ipv4mask 24 if1mac -1 \
|
|
if1ipv6 -1 if1ipv6mask 64 \
|
|
if2num -1 if2ipv4 -1 if2ipv4mask 24 if2mac -1 \
|
|
if2ipv6 -1 if2ipv6mask 64 }
|
|
set emuid1 -1
|
|
|
|
if { $prmsg==1 } { puts -nonewline "LINK(flags=$flags," }
|
|
|
|
#
|
|
# TLV parsing
|
|
#
|
|
while { $current < $len } {
|
|
# TLV header
|
|
if { [binary scan $data @${current}cc type length] != 2 } {
|
|
puts "TLV header error"
|
|
break
|
|
}
|
|
set length [expr {$length & 0xFF}]; # convert signed to unsigned
|
|
if { $length == 0 } {; # prevent endless looping
|
|
if { $type == 0 } { puts -nonewline "(extra padding)"; break
|
|
} else { puts "Found zero-length TLV for type=$type, dropping.";
|
|
break }
|
|
}
|
|
set pad [pad_32bit $length]
|
|
# verbose debugging
|
|
#puts "tlv type=$type length=$length pad=$pad current=$current"
|
|
incr current 2
|
|
|
|
if {![info exists typenames($type)] } { ;# unknown TLV type
|
|
if { $prmsg } { puts -nonewline "unknown=$type," }
|
|
incr current $length
|
|
continue
|
|
}
|
|
set typename $typenames($type)
|
|
set size $typesizes($typename)
|
|
# 32-bit and 64-bit vals pre-padded
|
|
if { $size == 4 || $size == 8} { incr current $pad }
|
|
# read TLV data depending on size
|
|
switch -exact -- "$size" {
|
|
2 { binary scan $data @${current}S vals($typename) }
|
|
4 { binary scan $data @${current}I vals($typename) }
|
|
8 { binary scan $data @${current}W vals($typename) }
|
|
16 { binary scan $data @${current}c16 vals($typename) }
|
|
-1 { binary scan $data @${current}a${length} vals($typename) }
|
|
}
|
|
incr current $length
|
|
# special handling of data here
|
|
switch -exact -- "$typename" {
|
|
delay -
|
|
jitter { if { $vals($typename) > 2000000 } {
|
|
array set vals [list $typename 2000000] } }
|
|
bw { if { $vals($typename) > 1000000000 } {
|
|
array set vals [list $typename 0] } }
|
|
per { if { $vals($typename) > 100 } {
|
|
array set vals [list $typename 100] } }
|
|
dup { if { $vals($typename) > 50 } {
|
|
array set vals [list $typename 50] } }
|
|
emuid1 { if { $emuid1 == -1 } {
|
|
set emuid $vals($typename)
|
|
} else { ;# this sets emuid2 if we already have emuid1
|
|
array set vals [list emuid2 $vals($typename) ]
|
|
array set vals [list emuid1 $emuid1 ]
|
|
}
|
|
}
|
|
if1ipv4 -
|
|
if2ipv4 { array set vals [list $typename \
|
|
[ipv4ToString $vals($typename)] ] }
|
|
if1mac -
|
|
if2mac { array set vals [list $typename \
|
|
[macToString $vals($typename)] ] }
|
|
if1ipv6 -
|
|
if2ipv6 { array set vals [list $typename \
|
|
[ipv6ToString $vals($typename)] ] }
|
|
}
|
|
if { $prmsg } { puts -nonewline "$typename=$vals($typename)," }
|
|
if { $size == 16 } { incr current $pad } ;# 128-bit vals post-padded
|
|
if { $size == -1 } { incr current $pad } ;# string vals post-padded
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ") " }
|
|
|
|
# perform some sanity checking of the link message
|
|
if { $vals(node1num) == $vals(node2num) || \
|
|
$vals(node1num) < 0 || $vals(node2num) < 0 } {
|
|
puts -nonewline "link message error - node1=$vals(node1num), "
|
|
puts "node2=$vals(node2num)"
|
|
return
|
|
}
|
|
|
|
# convert node number to node and check for node existance
|
|
set node1 "n$vals(node1num)"
|
|
set node2 "n$vals(node2num)"
|
|
if { [lsearch $node_list $node1] == -1 || \
|
|
[lsearch $node_list $node2] == -1 } {
|
|
puts "Node ($node1/$node2) in link message not found, dropping"
|
|
return
|
|
}
|
|
|
|
# set IPv4 and IPv6 address if specified, otherwise may be automatic
|
|
set prefix1 [chooseIfName $node1 $node2]
|
|
set prefix2 [chooseIfName $node2 $node1]
|
|
foreach i "1 2" {
|
|
# set interface name/number
|
|
if { $vals(if${i}num) == -1 } {
|
|
set ifname [newIfc [set prefix${i}] [set node${i}]]
|
|
set prefixlen [string length [set prefix${i}]]
|
|
set if${i}num [string range $ifname $prefixlen end]
|
|
array set vals [list if${i}num [set if${i}num]]
|
|
}
|
|
set ifname [set prefix${i}]$vals(if${i}num)
|
|
array set vals [list if${i}name $ifname]
|
|
# record IPv4/IPv6 addresses for newGUILink
|
|
foreach j "4 6" {
|
|
if { $vals(if${i}ipv${j}) != -1 } {
|
|
setIfcIPv${j}addr [set node${i}] $ifname \
|
|
$vals(if${i}ipv${j})/$vals(if${i}ipv${j}mask)
|
|
}
|
|
}
|
|
}
|
|
# adopt network address for WLAN (WLAN must be node 1)
|
|
if { [nodeType $node1] == "wlan" } {
|
|
set v4addr $vals(if2ipv4)
|
|
if { $v4addr != -1 } {
|
|
set v4net [ipv4ToNet $v4addr $vals(if2ipv4mask)]
|
|
setIfcIPv4addr $node1 wireless "$v4net/$vals(if2ipv4mask)"
|
|
}
|
|
set v6addr $vals(if2ipv6)
|
|
if { $v6addr != -1 } {
|
|
set v6net [ipv6ToNet $v6addr $vals(if2ipv6mask)]
|
|
setIfcIPv6addr $node1 wireless "${v6net}::0/$vals(if2ipv6mask)"
|
|
}
|
|
}
|
|
|
|
if { $execMode == "batch" } {
|
|
return ;# no GUI to update in batch mode
|
|
}
|
|
# treat 100% loss as link delete
|
|
if { $flags == 0 && $vals(per) == 100 } {
|
|
apiLinkDelete $node1 $node2 vals
|
|
return
|
|
}
|
|
|
|
# now we have all the information about this node
|
|
switch -exact -- "$flags" {
|
|
0 { apiLinkAddModify $node1 $node2 vals 0 }
|
|
1 { apiLinkAddModify $node1 $node2 vals 1 }
|
|
2 { apiLinkDelete $node1 $node2 vals }
|
|
default { puts "LINK: unsupported flags ($flags)"; return }
|
|
}
|
|
}
|
|
|
|
#
|
|
# add or modify a link
|
|
# if add flag is set, check if two nodes are part of same wlan, and do wlan
|
|
# linkage, or add a wired link; otherwise modify wired/wireless link with
|
|
# supplied parameters
|
|
proc apiLinkAddModify { node1 node2 vals_ref add } {
|
|
global eid defLinkWidth
|
|
set c .c
|
|
upvar $vals_ref vals
|
|
|
|
if {$vals(key) > -1} {
|
|
if { [nodeType $node1] == "tunnel" } {
|
|
netconfInsertSection $node1 [list "tunnel-key" $vals(key)]
|
|
}
|
|
if { [nodeType $node2] == "tunnel" } {
|
|
netconfInsertSection $node2 [list "tunnel-key" $vals(key)]
|
|
}
|
|
}
|
|
|
|
# look for a wired link in the link list
|
|
set wired_link [linkByPeers $node1 $node2]
|
|
if { $wired_link != "" && $add == 0 } { ;# wired link exists, modify it
|
|
#puts "modify wired link"
|
|
if { $vals(uni) == 1 } { ;# unidirectional link effects message
|
|
set peers [linkPeers $wired_link]
|
|
if { $node1 == [lindex $peers 0] } { ;# downstream n1 <-- n2
|
|
set bw [list $vals(bw) [getLinkBandwidth $wired_link up]]
|
|
set delay [list $vals(delay) [getLinkDelay $wired_link up]]
|
|
set per [list $vals(per) [getLinkBER $wired_link up]]
|
|
set dup [list $vals(dup) [getLinkBER $wired_link up]]
|
|
set jitter [list $vals(jitter) [getLinkJitter $wired_link up]]
|
|
} else { ;# upstream n1 --> n2
|
|
set bw [list [getLinkBandwidth $wired_link] $vals(bw)]
|
|
set delay [list [getLinkDelay $wired_link] $vals(delay)]
|
|
set per [list [getLinkBER $wired_link] $vals(per)]
|
|
set dup [list [getLinkBER $wired_link] $vals(dup)]
|
|
set jitter [list $vals(jitter) [getLinkJitter $wired_link]]
|
|
}
|
|
setLinkBandwidth $wired_link $bw
|
|
setLinkDelay $wired_link $delay
|
|
setLinkBER $wired_link $per
|
|
setLinkDup $wired_link $dup
|
|
setLinkJitter $wired_link $jitter
|
|
} else {
|
|
setLinkBandwidth $wired_link $vals(bw)
|
|
setLinkDelay $wired_link $vals(delay)
|
|
setLinkBER $wired_link $vals(per)
|
|
setLinkDup $wired_link $vals(dup)
|
|
setLinkJitter $wired_link $vals(jitter)
|
|
}
|
|
updateLinkLabel $wired_link
|
|
updateLinkGuiAttr $wired_link $vals(guiattr)
|
|
return
|
|
# if add flag is set and a wired link already exists, assume wlan linkage
|
|
# special case: rj45 model=1 means link via wireless
|
|
} elseif {[nodeType $node1] == "rj45" || [nodeType $node2] == "rj45"} {
|
|
if { [nodeType $node1] == "rj45" } {
|
|
set rj45node $node1; set othernode $node2;
|
|
} else { set rj45node $node2; set othernode $node1; }
|
|
if { [netconfFetchSection $rj45node model] == 1 } {
|
|
set wlan [findWlanNodes $othernode]
|
|
if {$wlan != ""} {newGUILink $wlan $rj45node};# link rj4node to wlan
|
|
}
|
|
}
|
|
|
|
# no wired link; determine if both nodes belong to the same wlan, and
|
|
# link them; otherwise add a wired link if add flag is set
|
|
set wlan $vals(netid)
|
|
if { $wlan < 0 } {
|
|
# WLAN not specified with netid, search for common WLAN
|
|
set wlans1 [findWlanNodes $node1]
|
|
set wlans2 [findWlanNodes $node2]
|
|
foreach w $wlans1 {
|
|
if { [lsearch -exact $wlans2 $w] < 0 } { continue }
|
|
set wlan $w
|
|
break
|
|
}
|
|
}
|
|
|
|
if { $wlan < 0 } { ;# no common wlan
|
|
if {$add == 1} { ;# add flag was set - add a wired link
|
|
global g_newLink_ifhints
|
|
set g_newLink_ifhints [list $vals(if1name) $vals(if2name)]
|
|
newGUILink $node1 $node2
|
|
if { [getNodeCanvas $node1] != [getNodeCanvas $node2] } {
|
|
set wired_link [linkByPeersMirror $node1 $node2]
|
|
} else {
|
|
set wired_link [linkByPeers $node1 $node2]
|
|
}
|
|
setLinkBandwidth $wired_link $vals(bw)
|
|
setLinkDelay $wired_link $vals(delay)
|
|
setLinkBER $wired_link $vals(per)
|
|
setLinkDup $wired_link $vals(dup)
|
|
setLinkJitter $wired_link $vals(jitter)
|
|
updateLinkLabel $wired_link
|
|
updateLinkGuiAttr $wired_link $vals(guiattr)
|
|
# adopt link effects for WLAN (WLAN must be node 1)
|
|
if { [nodeType $node1] == "wlan" } {
|
|
setLinkBandwidth $node1 $vals(bw)
|
|
setLinkDelay $node1 $vals(delay)
|
|
setLinkBER $node1 $vals(per)
|
|
}
|
|
return
|
|
} else { ;# modify link, but no wired link or common wlan!
|
|
puts -nonewline "link modify message received, but no wired link"
|
|
puts " or wlan for nodes $node1-$node2, dropping"
|
|
return
|
|
}
|
|
}
|
|
|
|
set wlan "n$wlan"
|
|
drawWlanLink $node1 $node2 $wlan
|
|
}
|
|
|
|
#
|
|
# delete a link
|
|
#
|
|
proc apiLinkDelete { node1 node2 vals_ref } {
|
|
global eid
|
|
upvar $vals_ref vals
|
|
set c .c
|
|
|
|
# look for a wired link in the link list
|
|
set wired_link [linkByPeers $node1 $node2]
|
|
if { $wired_link != "" } {
|
|
removeGUILink $wired_link non-atomic
|
|
return
|
|
}
|
|
|
|
set wlan $vals(netid)
|
|
if { $wlan < 0 } {
|
|
# WLAN not specified with netid, search for common WLAN
|
|
set wlans1 [findWlanNodes $node1]
|
|
set wlans2 [findWlanNodes $node2]
|
|
foreach w $wlans1 {
|
|
if { [lsearch -exact $wlans2 $w] < 0 } { continue }
|
|
set wlan $w
|
|
break
|
|
}
|
|
}
|
|
if { $wlan < 0 } {
|
|
puts "apiLinkDelete: no common WLAN!"
|
|
return
|
|
}
|
|
set wlan "n$wlan"
|
|
|
|
# look for wireless link on the canvas, remove GUI object
|
|
$c delete -withtags "wlanlink && $node2 && $node1 && $wlan"
|
|
$c delete -withtags "linklabel && $node2 && $node1 && $wlan"
|
|
}
|
|
|
|
#
|
|
# CORE API Execute message TLVs
|
|
#
|
|
proc parseExecMessage { data len flags channel } {
|
|
global node_list curcanvas c router eid showAPI
|
|
global XSCALE YSCALE XOFFSET YOFFSET
|
|
set prmsg $showAPI
|
|
set current 0
|
|
|
|
# set default values
|
|
set nodenum 0
|
|
set execnum 0
|
|
set exectime 0
|
|
set execcmd ""
|
|
set execres ""
|
|
set execstatus 0
|
|
set session ""
|
|
|
|
if { $prmsg==1 } { puts -nonewline "EXEC(flags=$flags," }
|
|
|
|
# parse each TLV
|
|
while { $current < $len } {
|
|
# TLV header
|
|
set typelength [parseTLVHeader $data current]
|
|
set type [lindex $typelength 0]
|
|
set length [lindex $typelength 1]
|
|
if { $length == 0 || $length == "" } { break }
|
|
set pad [pad_32bit $length]
|
|
# verbose debugging
|
|
#puts "exec tlv type=$type length=$length pad=$pad current=$current"
|
|
if { [expr {$current + $length + $pad}] > $len } {
|
|
puts "error with EXEC message length (len=$len, TLV length=$length)"
|
|
break
|
|
}
|
|
# TLV data
|
|
switch -exact -- "$type" {
|
|
1 {
|
|
incr current $pad
|
|
binary scan $data @${current}I nodenum
|
|
if { $prmsg==1 } { puts -nonewline "node=$nodenum/" }
|
|
}
|
|
2 {
|
|
incr current $pad
|
|
binary scan $data @${current}I execnum
|
|
if { $prmsg == 1} { puts -nonewline "exec=$execnum," }
|
|
}
|
|
3 {
|
|
incr current $pad
|
|
binary scan $data @${current}I exectime
|
|
if { $prmsg == 1} { puts -nonewline "time=$exectime," }
|
|
}
|
|
4 {
|
|
binary scan $data @${current}a${length} execcmd
|
|
if { $prmsg == 1} { puts -nonewline "cmd=$execcmd," }
|
|
incr current $pad
|
|
}
|
|
5 {
|
|
binary scan $data @${current}a${length} execres
|
|
if { $prmsg == 1} { puts -nonewline "res=($length bytes)," }
|
|
incr current $pad
|
|
}
|
|
6 {
|
|
incr current $pad
|
|
binary scan $data @${current}I execstatus
|
|
if { $prmsg == 1} { puts -nonewline "status=$execstatus," }
|
|
}
|
|
10 {
|
|
binary scan $data @${current}a${length} session
|
|
if { $prmsg == 1} { puts -nonewline "session=$session," }
|
|
incr current $pad
|
|
}
|
|
default {
|
|
if { $prmsg == 1} { puts -nonewline "unknown=" }
|
|
if { $prmsg == 1} { puts -nonewline "$type," }
|
|
}
|
|
}
|
|
# end switch
|
|
|
|
# advance current pointer
|
|
incr current $length
|
|
}
|
|
if { $prmsg == 1 } { puts ") "}
|
|
|
|
set node "n$nodenum"
|
|
set node_id "$eid\_$node"
|
|
# check for node existance
|
|
if { [lsearch $node_list $node] == -1 } {
|
|
puts "Execute message but node ($node) does not exist, dropping."
|
|
return
|
|
}
|
|
global $node
|
|
|
|
# Callback support - match execnum from response with original request, and
|
|
# invoke type-specific callback
|
|
global g_execRequests
|
|
foreach type [array names g_execRequests] {
|
|
set idx [lsearch $g_execRequests($type) $execnum]
|
|
if { $idx > -1 } {
|
|
set g_execRequests($type) \
|
|
[lreplace $g_execRequests($type) $idx $idx]
|
|
exec_${type}_callback $node $execnum $execcmd $execres $execstatus
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
# spawn interactive terminal
|
|
proc exec_shell_callback { node execnum execcmd execres execstatus } {
|
|
#puts "opening terminal for $node by running '$execres'"
|
|
set title "CORE: [getNodeName $node] (console)"
|
|
set term [get_term_prog false]
|
|
set xi [string first "xterm -e" $execres]
|
|
|
|
# shell callback already has xterm command, launch it using user-defined
|
|
# term program (e.g. remote nodes 'ssh -X -f a.b.c.d xterm -e ...'
|
|
if { $xi > -1 } {
|
|
set execres [string replace $execres $xi [expr $xi+7] $term]
|
|
if { [catch {exec sh -c "$execres" & } ] } {
|
|
puts "Warning: failed to open terminal for $node"
|
|
}
|
|
return
|
|
# no xterm command; execute shell callback in a terminal (e.g. local nodes)
|
|
} elseif { \
|
|
[catch {eval exec $term "$execres" & } ] } {
|
|
puts "Warning: failed to open terminal for $node: ($term $execres)"
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# CORE API Register message TLVs
|
|
# parse register message into plugin capabilities
|
|
#
|
|
proc parseRegMessage { data len flags channel } {
|
|
global regntypes showAPI
|
|
set prmsg $showAPI
|
|
set current 0
|
|
set str 0
|
|
set session ""
|
|
set fnhint ""
|
|
|
|
set plugin_cap_list {} ;# plugin capabilities list
|
|
|
|
if { $prmsg==1 } { puts -nonewline "REG(flags=$flags," }
|
|
|
|
# parse each TLV
|
|
while { $current < $len } {
|
|
# TLV header
|
|
if { [binary scan $data @${current}cc type length] != 2 } {
|
|
puts "TLV header error"
|
|
break
|
|
}
|
|
set length [expr {$length & 0xFF}]; # convert signed to unsigned
|
|
if { $length == 0 } {
|
|
# prevent endless looping
|
|
if { $type == 0 } {
|
|
puts -nonewline "(extra padding)"
|
|
break
|
|
} else {
|
|
puts "Found zero-length TLV for type=$type, dropping."
|
|
break
|
|
}
|
|
}
|
|
set pad [pad_32bit $length]
|
|
# verbose debugging
|
|
#puts "tlv type=$type length=$length pad=$pad current=$current"
|
|
incr current 2
|
|
# TLV data
|
|
if { [info exists regntypes($type)] } {
|
|
set plugin_type $regntypes($type)
|
|
binary scan $data @${current}a${length} str
|
|
if { $prmsg == 1} { puts -nonewline "$plugin_type=$str," }
|
|
if { $type == 10 } { ;# session number
|
|
set session $str
|
|
} else {
|
|
lappend plugin_cap_list "$plugin_type=$str"
|
|
if { $plugin_type == "exec" } { set fnhint $str }
|
|
}
|
|
} else {
|
|
if { $prmsg == 1} { puts -nonewline "unknown($type)," }
|
|
}
|
|
incr current $pad
|
|
# end switch
|
|
|
|
# advance current pointer
|
|
incr current $length
|
|
}
|
|
if { $prmsg == 1 } { puts ") "}
|
|
|
|
# reg message with session number indicates the sid of a session that
|
|
# was just started from XML or Python script (via reg exec=scriptfile.py)
|
|
if { $session != "" } {
|
|
# assume session string only contains one session number
|
|
connectShutdownSession connect $channel $session $fnhint
|
|
return
|
|
}
|
|
|
|
set plugin [pluginByChannel $channel]
|
|
if { [setPluginCapList $plugin $plugin_cap_list] < 0 } {
|
|
return
|
|
}
|
|
|
|
# callback to refresh any open dialogs this message may refresh
|
|
pluginsConfigRefreshCallback
|
|
}
|
|
|
|
proc parseConfMessage { data len flags channel } {
|
|
global showAPI node_list MACHINE_TYPES
|
|
set prmsg $showAPI
|
|
set current 0
|
|
set str 0
|
|
set nodenum -1
|
|
set obj ""
|
|
set tflags 0
|
|
set types {}
|
|
set values {}
|
|
set captions {}
|
|
set bitmap {}
|
|
set possible_values {}
|
|
set groups {}
|
|
set opaque {}
|
|
set session ""
|
|
set netid -1
|
|
|
|
if { $prmsg==1 } { puts -nonewline "CONF(flags=$flags," }
|
|
|
|
# parse each TLV
|
|
while { $current < $len } {
|
|
set typelength [parseTLVHeader $data current]
|
|
set type [lindex $typelength 0]
|
|
set length [lindex $typelength 1]
|
|
set pad [pad_32bit $length]
|
|
if { $length == 0 || $length == "" } {
|
|
# allow some zero-length string TLVs
|
|
if { $type < 5 || $type > 9 } { break }
|
|
}
|
|
# verbose debugging
|
|
#puts "tlv type=$type length=$length pad=$pad current=$current"
|
|
# TLV data
|
|
switch -exact -- "$type" {
|
|
1 {
|
|
incr current $pad
|
|
binary scan $data @${current}I nodenum
|
|
if { $prmsg == 1} { puts -nonewline "node=$nodenum/" }
|
|
}
|
|
2 {
|
|
binary scan $data @${current}a${length} obj
|
|
if { $prmsg == 1} { puts -nonewline "obj=$obj," }
|
|
incr current $pad
|
|
}
|
|
3 {
|
|
binary scan $data @${current}S tflags
|
|
if { $prmsg == 1} { puts -nonewline "cflags=$tflags," }
|
|
}
|
|
4 {
|
|
set type 0
|
|
set types {}
|
|
if { $prmsg == 1} { puts -nonewline "types=" }
|
|
# number of 16-bit values
|
|
set types_len $length
|
|
# get each 16-bit type value, add to list
|
|
while {$types_len > 0} {
|
|
binary scan $data @${current}S type
|
|
if {$type > 0 && $type < 12} {
|
|
lappend types $type
|
|
if { $prmsg == 1} { puts -nonewline "$type/" }
|
|
}
|
|
incr current 2
|
|
incr types_len -2
|
|
}
|
|
if { $prmsg == 1} { puts -nonewline "," }
|
|
incr current -$length; # length incremented below
|
|
incr current $pad
|
|
}
|
|
5 {
|
|
set values {}
|
|
binary scan $data @${current}a${length} vals
|
|
if { $prmsg == 1} { puts -nonewline "vals=$vals," }
|
|
set values [split $vals |]
|
|
incr current $pad
|
|
}
|
|
6 {
|
|
set captions {}
|
|
binary scan $data @${current}a${length} capt
|
|
if { $prmsg == 1} { puts -nonewline "capt=$capt," }
|
|
set captions [split $capt |]
|
|
incr current $pad
|
|
}
|
|
7 {
|
|
set bitmap {}
|
|
binary scan $data @${current}a${length} bitmap
|
|
if { $prmsg == 1} { puts -nonewline "bitmap," }
|
|
incr current $pad
|
|
}
|
|
8 {
|
|
set possible_values {}
|
|
binary scan $data @${current}a${length} pvals
|
|
if { $prmsg == 1} { puts -nonewline "pvals=$pvals," }
|
|
set possible_values [split $pvals |]
|
|
incr current $pad
|
|
}
|
|
9 {
|
|
set groups {}
|
|
binary scan $data @${current}a${length} groupsstr
|
|
if { $prmsg == 1} { puts -nonewline "groups=$groupsstr," }
|
|
set groups [split $groupsstr |]
|
|
incr current $pad
|
|
}
|
|
10 {
|
|
binary scan $data @${current}a${length} session
|
|
if { $prmsg == 1} { puts -nonewline "session=$session," }
|
|
incr current $pad
|
|
}
|
|
35 {
|
|
incr current $pad
|
|
binary scan $data @${current}I netid
|
|
if { $prmsg == 1} { puts -nonewline "netid=$netid/" }
|
|
}
|
|
80 {
|
|
set opaque {}
|
|
binary scan $data @${current}a${length} opaquestr
|
|
if { $prmsg == 1} { puts -nonewline "opaque=$opaquestr," }
|
|
set opaque [split $opaquestr |]
|
|
incr current $pad
|
|
}
|
|
default {
|
|
if { $prmsg == 1} { puts -nonewline "unknown=" }
|
|
if { $prmsg == 1} { puts -nonewline "$type," }
|
|
}
|
|
}
|
|
# end switch
|
|
|
|
# advance current pointer
|
|
incr current $length
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ") "}
|
|
|
|
set objs_ok [concat "services session metadata emane" $MACHINE_TYPES]
|
|
if { $nodenum > -1 } {
|
|
set node "n$nodenum"
|
|
} else {
|
|
set node ""
|
|
}
|
|
# check for node existance
|
|
if { [lsearch $node_list $node] == -1 } {
|
|
if { [lsearch $objs_ok $obj] < 0 } {
|
|
set msg "Configure message for $obj but node ($node) does"
|
|
set msg "$msg not exist, dropping."
|
|
puts $msg
|
|
return
|
|
}
|
|
} else {
|
|
global $node
|
|
}
|
|
|
|
# for handling node services
|
|
# this could be improved, instead of checking for the hard-coded object
|
|
# "services" and opaque data for service customization
|
|
if { $obj == "services" } {
|
|
if { $tflags & 0x2 } { ;# update flag
|
|
if { $opaque != "" } {
|
|
set services [lindex [split $opaque ":"] 1]
|
|
set services [split $services ","]
|
|
customizeServiceValues n$nodenum $values $services
|
|
}
|
|
# TODO: save services config with the node
|
|
} elseif { $tflags & 0x1 } { ;# request flag
|
|
# TODO: something else
|
|
} else {
|
|
popupServicesConfig $channel n$nodenum $types $values $captions \
|
|
$possible_values $groups $session
|
|
}
|
|
return
|
|
# metadata received upon XML file load
|
|
} elseif { $obj == "metadata" } {
|
|
parseMetaData $values
|
|
return
|
|
# session options received upon XML file load
|
|
} elseif { $obj == "session" && $tflags & 0x2 } {
|
|
setSessionOptions $types $values
|
|
return
|
|
}
|
|
# handle node machine-type profile
|
|
if { [lsearch $MACHINE_TYPES $obj] != -1 } {
|
|
if { $tflags == 0 } {
|
|
popupNodeProfileConfig $channel n$nodenum $obj $types $values \
|
|
$captions $bitmap $possible_values $groups $session \
|
|
$opaque
|
|
} else {
|
|
puts -nonewline "warning: received Configure message for profile "
|
|
puts "with unexpected flags!"
|
|
}
|
|
return
|
|
}
|
|
|
|
# update the configuration for a node without displaying dialog box
|
|
if { $tflags & 0x2 } {
|
|
if { $obj == "emane" && $node == "" } {
|
|
set node [lindex [findWlanNodes ""] 0]
|
|
}
|
|
if { $node == "" } {
|
|
puts "ignoring Configure message for $obj with no node"
|
|
return
|
|
}
|
|
# this is similar to popupCapabilityConfigApply
|
|
setCustomConfig $node $obj $types $values 0
|
|
if { $obj != "emane" && [nodeType $node] == "wlan"} {
|
|
set section [list mobmodel coreapi $obj]
|
|
netconfInsertSection $node $section
|
|
}
|
|
# configuration request - unhandled
|
|
} elseif { $tflags & 0x1 } {
|
|
# configuration response data from our request (from GUI plugin configure)
|
|
} else {
|
|
popupCapabilityConfig $channel n$nodenum $obj $types $values \
|
|
$captions $bitmap $possible_values $groups
|
|
}
|
|
}
|
|
|
|
# process metadata received from Conf Message when loading XML
|
|
proc parseMetaData { values } {
|
|
global canvas_list annotation_list execMode g_comments
|
|
|
|
foreach value $values {
|
|
# data looks like this: "annotation a1={iconcoords {514.0 132.0...}}"
|
|
lassign [splitKeyValue $value] key object_config
|
|
lassign $key class object
|
|
# metadata with no object name e.g. comments="Comment text"
|
|
if { "$class" == "comments" } {
|
|
set g_comments $object_config
|
|
continue
|
|
} elseif { "$class" == "global_options" } {
|
|
foreach opt $object_config {
|
|
lassign [split $opt =] key value
|
|
setGlobalOption $key $value
|
|
}
|
|
continue
|
|
}
|
|
# metadata having class and object name
|
|
if {"$class" == "" || $object == ""} {
|
|
puts "warning: invalid metadata value '$value'"
|
|
}
|
|
if { "$class" == "canvas" } {
|
|
if { [lsearch $canvas_list $object] < 0 } {
|
|
lappend canvas_list $object
|
|
}
|
|
} elseif { "$class" == "annotation" } {
|
|
if { [lsearch $annotation_list $object] < 0 } {
|
|
lappend annotation_list $object
|
|
}
|
|
} else {
|
|
puts "metadata parsing error: unknown object class $class"
|
|
}
|
|
global $object
|
|
set $object $object_config
|
|
}
|
|
|
|
if { $execMode == "batch" } { return }
|
|
switchCanvas none
|
|
redrawAll
|
|
}
|
|
|
|
proc parseFileMessage { data len flags channel } {
|
|
global showAPI node_list
|
|
set prmsg $showAPI
|
|
|
|
array set tlvnames { 1 num 2 name 3 mode 4 fno 5 type 6 sname \
|
|
10 session 16 data 17 cdata }
|
|
array set tlvsizes { num 4 name -1 mode -3 fno 2 type -1 sname -1 \
|
|
session -1 data -1 cdata -1 }
|
|
array set defvals { num -1 name "" mode -1 fno -1 type "" sname "" \
|
|
session "" data "" cdata "" }
|
|
|
|
if { $prmsg==1 } { puts -nonewline "FILE(flags=$flags," }
|
|
array set vals [parseMessage $data $len $flags [array get tlvnames] \
|
|
[array get tlvsizes] [array get defvals]]
|
|
if { $prmsg } { puts ") "}
|
|
|
|
# hook scripts received in File Message
|
|
if { [string range $vals(type) 0 4] == "hook:" } {
|
|
global g_hook_scripts
|
|
set state [string range $vals(type) 5 end]
|
|
lappend g_hook_scripts [list $vals(name) $state $vals(data)]
|
|
return
|
|
}
|
|
|
|
# required fields
|
|
foreach t "num name data" {
|
|
if { $vals($t) == $defvals($t) } {
|
|
puts "Received File Message without $t, dropping."; return;
|
|
}
|
|
}
|
|
|
|
# check for node existance
|
|
set node "n$vals(num)"
|
|
if { [lsearch $node_list $node] == -1 } {
|
|
puts "File message but node ($node) does not exist, dropping."
|
|
return
|
|
} else {
|
|
global $node
|
|
}
|
|
|
|
# service customization received in File Message
|
|
if { [string range $vals(type) 0 7] == "service:" } {
|
|
customizeServiceFile $node $vals(name) $vals(type) $vals(data) true
|
|
}
|
|
}
|
|
|
|
proc parseEventMessage { data len flags channel } {
|
|
global showAPI eventtypes g_traffic_start_opt execMode node_list
|
|
set prmsg $showAPI
|
|
set current 0
|
|
set nodenum -1
|
|
set eventtype -1
|
|
set eventname ""
|
|
set eventdata ""
|
|
set eventtime ""
|
|
set session ""
|
|
|
|
if { $prmsg==1 } { puts -nonewline "EVENT(flags=$flags," }
|
|
|
|
# parse each TLV
|
|
while { $current < $len } {
|
|
set typelength [parseTLVHeader $data current]
|
|
set type [lindex $typelength 0]
|
|
set length [lindex $typelength 1]
|
|
if { $length == 0 || $length == "" } { break }
|
|
set pad [pad_32bit $length]
|
|
# verbose debugging
|
|
#puts "tlv type=$type length=$length pad=$pad current=$current"
|
|
# TLV data
|
|
switch -exact -- "$type" {
|
|
1 {
|
|
incr current $pad
|
|
binary scan $data @${current}I nodenum
|
|
if { $prmsg == 1} { puts -nonewline "node=$nodenum," }
|
|
}
|
|
2 {
|
|
incr current $pad
|
|
binary scan $data @${current}I eventtype
|
|
if { $prmsg == 1} {
|
|
set typestr ""
|
|
foreach t [array names eventtypes] {
|
|
if { $eventtypes($t) == $eventtype } {
|
|
set typestr "-$t"
|
|
break
|
|
}
|
|
}
|
|
puts -nonewline "type=$eventtype$typestr,"
|
|
}
|
|
}
|
|
3 {
|
|
binary scan $data @${current}a${length} eventname
|
|
if { $prmsg == 1} { puts -nonewline "name=$eventname," }
|
|
incr current $pad
|
|
}
|
|
4 {
|
|
binary scan $data @${current}a${length} eventdata
|
|
if { $prmsg == 1} { puts -nonewline "data=$eventdata," }
|
|
incr current $pad
|
|
}
|
|
5 {
|
|
binary scan $data @${current}a${length} eventtime
|
|
if { $prmsg == 1} { puts -nonewline "time=$eventtime," }
|
|
incr current $pad
|
|
}
|
|
10 {
|
|
binary scan $data @${current}a${length} session
|
|
if { $prmsg == 1} { puts -nonewline "session=$session," }
|
|
incr current $pad
|
|
}
|
|
default {
|
|
if { $prmsg == 1} { puts -nonewline "unknown=" }
|
|
if { $prmsg == 1} { puts -nonewline "$type," }
|
|
}
|
|
}
|
|
# end switch
|
|
|
|
# advance current pointer
|
|
incr current $length
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ") "}
|
|
|
|
# TODO: take other actions here based on Event Message
|
|
if { $eventtype == 4 } { ;# entered the runtime state
|
|
if { $g_traffic_start_opt == 1 } { startTrafficScripts }
|
|
if { $execMode == "batch" } {
|
|
global g_current_session
|
|
puts "disconnecting. Session id is $g_current_session"
|
|
exit.real
|
|
}
|
|
} elseif { $eventtype == 6 } { ;# shutdown state
|
|
set name [lindex [getEmulPlugin "*"] 0]
|
|
if { [getAssignedRemoteServers] == "" } {
|
|
# start a new session if not distributed
|
|
# otherwise we need to allow time for node delete messages
|
|
# from other servers
|
|
pluginConnect $name disconnect 1
|
|
pluginConnect $name connect 1
|
|
}
|
|
} elseif { $eventtype >= 7 || $eventtype <= 10 } {
|
|
if { [string range $eventname 0 8] == "mobility:" } {
|
|
set node "n$nodenum"
|
|
if {[lsearch $node_list $node] == -1} {
|
|
puts "Event message with unknown node %nodenum."
|
|
return
|
|
}
|
|
handleMobilityScriptEvent $node $eventtype $eventdata $eventtime
|
|
}
|
|
}
|
|
}
|
|
|
|
proc parseSessionMessage { data len flags channel } {
|
|
global showAPI g_current_session g_session_dialog_hint execMode
|
|
set prmsg $showAPI
|
|
set current 0
|
|
set sessionids {}
|
|
set sessionnames {}
|
|
set sessionfiles {}
|
|
set nodecounts {}
|
|
set sessiondates {}
|
|
set thumbs {}
|
|
set sessionopaque {}
|
|
|
|
if { $prmsg==1 } { puts -nonewline "SESSION(flags=$flags," }
|
|
|
|
# parse each TLV
|
|
while { $current < $len } {
|
|
set typelength [parseTLVHeader $data current]
|
|
set type [lindex $typelength 0]
|
|
set length [lindex $typelength 1]
|
|
if { $length == 0 || $length == "" } {
|
|
puts "warning: zero-length TLV, discarding remainder of message!"
|
|
break
|
|
}
|
|
set pad [pad_32bit $length]
|
|
# verbose debugging
|
|
#puts "tlv type=$type length=$length pad=$pad current=$current"
|
|
# TLV data
|
|
switch -exact -- "$type" {
|
|
1 {
|
|
set sessionids {}
|
|
binary scan $data @${current}a${length} sids
|
|
if { $prmsg == 1} { puts -nonewline "sids=$sids," }
|
|
set sessionids [split $sids |]
|
|
incr current $pad
|
|
}
|
|
2 {
|
|
set sessionnames {}
|
|
binary scan $data @${current}a${length} snames
|
|
if { $prmsg == 1} { puts -nonewline "names=$snames," }
|
|
set sessionnames [split $snames |]
|
|
incr current $pad
|
|
}
|
|
3 {
|
|
set sessionfiles {}
|
|
binary scan $data @${current}a${length} sfiles
|
|
if { $prmsg == 1} { puts -nonewline "files=$sfiles," }
|
|
set sessionfiles [split $sfiles |]
|
|
incr current $pad
|
|
}
|
|
4 {
|
|
set nodecounts {}
|
|
binary scan $data @${current}a${length} ncs
|
|
if { $prmsg == 1} { puts -nonewline "ncs=$ncs," }
|
|
set nodecounts [split $ncs |]
|
|
incr current $pad
|
|
}
|
|
5 {
|
|
set sessiondates {}
|
|
binary scan $data @${current}a${length} sdates
|
|
if { $prmsg == 1} { puts -nonewline "dates=$sdates," }
|
|
set sessiondates [split $sdates |]
|
|
incr current $pad
|
|
}
|
|
6 {
|
|
set thumbs {}
|
|
binary scan $data @${current}a${length} th
|
|
if { $prmsg == 1} { puts -nonewline "thumbs=$th," }
|
|
set thumbs [split $th |]
|
|
incr current $pad
|
|
}
|
|
10 {
|
|
set sessionopaque {}
|
|
binary scan $data @${current}a${length} sessionopaque
|
|
if { $prmsg == 1} { puts -nonewline "$sessionopaque," }
|
|
incr current $pad
|
|
}
|
|
default {
|
|
if { $prmsg == 1} { puts -nonewline "unknown=" }
|
|
if { $prmsg == 1} { puts -nonewline "$type," }
|
|
}
|
|
}
|
|
# end switch
|
|
|
|
# advance current pointer
|
|
incr current $length
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ") "}
|
|
|
|
if {$g_current_session == 0} {
|
|
# set the current session to the channel port number
|
|
set current_session [lindex [fconfigure $channel -sockname] 2]
|
|
} else {
|
|
set current_session $g_current_session
|
|
}
|
|
|
|
if {[lsearch $sessionids $current_session] == -1} {
|
|
puts -nonewline "*** warning: current session ($g_current_session) "
|
|
puts "not found in session list: $sessionids"
|
|
}
|
|
|
|
set orig_session_choice $g_current_session
|
|
set g_current_session $current_session
|
|
setGuiTitle ""
|
|
|
|
if {$execMode == "closebatch"} {
|
|
# we're going to close some session, so this is expected
|
|
global g_session_choice
|
|
|
|
if {[lsearch $sessionids $g_session_choice] == -1} {
|
|
puts -nonewline "*** warning: current session ($g_session_choice) "
|
|
puts "not found in session list: $sessionids"
|
|
} else {
|
|
set flags 0x2 ;# delete flag
|
|
set sid $g_session_choice
|
|
set name ""
|
|
set f ""
|
|
set nodecount ""
|
|
set thumb ""
|
|
set user ""
|
|
sendSessionMessage $channel $flags $sid $name $f $nodecount $thumb $user
|
|
|
|
puts "Session shutdown message sent."
|
|
}
|
|
exit.real
|
|
}
|
|
|
|
if {$orig_session_choice == 0 && [llength $sessionids] == 1} {
|
|
# we just started up and only the current session exists
|
|
set g_session_dialog_hint 0
|
|
return
|
|
}
|
|
|
|
if {$execMode == "batch"} {
|
|
puts "Another session is active."
|
|
exit.real
|
|
}
|
|
|
|
if { $g_session_dialog_hint } {
|
|
popupSessionConfig $channel $sessionids $sessionnames $sessionfiles \
|
|
$nodecounts $sessiondates $thumbs $sessionopaque
|
|
}
|
|
set g_session_dialog_hint 0
|
|
}
|
|
|
|
# parse message TLVs given the possible TLV names and sizes
|
|
# default values are supplied in defaultvals, parsed values are returned
|
|
proc parseMessage { data len flags tlvnamesl tlvsizesl defaultvalsl } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
|
|
array set tlvnames $tlvnamesl
|
|
array set tlvsizes $tlvsizesl
|
|
array set vals $defaultvalsl ;# this array is returned
|
|
|
|
set current 0
|
|
|
|
while { $current < $len } {
|
|
set typelength [parseTLVHeader $data current]
|
|
set type [lindex $typelength 0]
|
|
set length [lindex $typelength 1]
|
|
if { $length == 0 || $length == "" } { break }
|
|
set pad [pad_32bit $length]
|
|
|
|
if {![info exists tlvnames($type)] } { ;# unknown TLV type
|
|
if { $prmsg } { puts -nonewline "unknown=$type," }
|
|
incr current $length
|
|
continue
|
|
}
|
|
set tlvname $tlvnames($type)
|
|
set size $tlvsizes($tlvname)
|
|
# 32-bit and 64-bit vals pre-padded
|
|
if { $size == 4 || $size == 8 } { incr current $pad }
|
|
# read TLV data depending on size
|
|
switch -exact -- "$size" {
|
|
2 { binary scan $data @${current}S vals($tlvname) }
|
|
4 { binary scan $data @${current}I vals($tlvname) }
|
|
8 { binary scan $data @${current}W vals($tlvname) }
|
|
16 { binary scan $data @${current}c16 vals($tlvname) }
|
|
-1 { binary scan $data @${current}a${length} vals($tlvname) }
|
|
}
|
|
if { $size == -1 } { incr current $pad } ;# string vals post-padded
|
|
if { $type == 6 } { incr current $pad } ;# 128-bit vals post-padded
|
|
incr current $length
|
|
|
|
if { $prmsg } { puts -nonewline "$tlvname=$vals($tlvname)," }
|
|
}
|
|
return [array get vals]
|
|
}
|
|
|
|
proc parseExceptionMessage { data len flags channel } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
|
|
array set typenames { 1 num 2 sess 3 level 4 src 5 date 6 txt 10 opaque }
|
|
array set typesizes { num 4 sess -1 level 2 src -1 date -1 txt -1 \
|
|
opaque -1 }
|
|
array set defvals { num -1 sess "" level -1 src "" date "" txt "" opaque ""}
|
|
|
|
if { $prmsg==1 } { puts -nonewline "EXCEPTION(flags=$flags," }
|
|
array set vals [parseMessage $data $len $flags [array get typenames] \
|
|
[array get typesizes] [array get defvals]]
|
|
if { $prmsg == 1 } { puts ") "}
|
|
|
|
if { $vals(level) == $defvals(level) } {
|
|
puts "Exception Message received without an exception level."; return;
|
|
}
|
|
|
|
receiveException [array get vals]
|
|
}
|
|
|
|
proc sendNodePosMessage { channel node nodeid x y wlanid force } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
|
|
if { $channel == -1 } {
|
|
set channel [lindex [getEmulPlugin $node] 2]
|
|
if { $channel == -1 } { return }
|
|
}
|
|
set node_num [string range $node 1 end]
|
|
set x [format "%u" [expr int($x)]]
|
|
set y [format "%u" [expr int($y)]]
|
|
set len [expr 8+4+4] ;# node number, x, y
|
|
if {$nodeid > -1} { incr len 8 }
|
|
if {$wlanid > -1} { incr len 8 }
|
|
if {$force == 1 } { set crit 0x4 } else { set crit 0x0 }
|
|
#puts "sending [expr $len+4] bytes: $nodeid $x $y $wlanid"
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">NODE(flags=$crit,$node,x=$x,y=$y" }
|
|
set msg [binary format ccSc2sIc2Sc2S \
|
|
1 $crit $len \
|
|
{1 4} 0 $node_num \
|
|
{0x20 2} $x \
|
|
{0x21 2} $y
|
|
]
|
|
|
|
set msg2 ""
|
|
set msg3 ""
|
|
if { $nodeid > -1 } {
|
|
if { $prmsg == 1 } { puts -nonewline ",emuid=$nodeid" }
|
|
set msg2 [binary format c2sI {0x23 4} 0 $nodeid]
|
|
}
|
|
if { $wlanid > -1 } {
|
|
if { $prmsg == 1 } { puts -nonewline ",netid=$wlanid" }
|
|
set msg3 [binary format c2sI {0x24 4} 0 $wlanid]
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ")" }
|
|
puts -nonewline $channel $msg$msg2$msg3
|
|
flushChannel channel "Error sending node position"
|
|
}
|
|
|
|
# build a new node
|
|
proc sendNodeAddMessage { channel node } {
|
|
global showAPI CORE_DATA_DIR
|
|
set prmsg $showAPI
|
|
set len [expr {8+8+4+4}]; # node number, type, x, y
|
|
set ipv4 0
|
|
set ipv6 0
|
|
set macstr ""
|
|
set wireless 0
|
|
|
|
# type, name
|
|
set type [getNodeTypeAPI $node]
|
|
set model [getNodeModel $node]
|
|
set model_len [string length $model]
|
|
set model_pad_len [pad_32bit $model_len]
|
|
set model_pad [binary format x$model_pad_len]
|
|
set name [getNodeName $node]
|
|
set name_len [string length $name]
|
|
set name_pad_len [pad_32bit $name_len]
|
|
set name_pad [binary format x$name_pad_len]
|
|
incr len [expr { 2+$name_len+$name_pad_len}]
|
|
if {$model_len > 0} { incr len [expr {2+$model_len+$model_pad_len }] }
|
|
set node_num [string range $node 1 end]
|
|
|
|
# fixup node type for EMANE-enabled WLAN nodes
|
|
set opaque ""
|
|
if { [isEmane $node] } { set type 0xA }
|
|
|
|
# emulation server (node location)
|
|
set emusrv [getNodeLocation $node]
|
|
set emusrv_len [string length $emusrv]
|
|
set emusrv_pad_len [pad_32bit $emusrv_len]
|
|
set emusrv_pad [binary format x$emusrv_pad_len]
|
|
if { $emusrv_len > 0 } { incr len [expr {2+$emusrv_len+$emusrv_pad_len } ] }
|
|
|
|
# canvas
|
|
set canv [getNodeCanvas $node]
|
|
if { $canv != "c1" } {
|
|
set canv [string range $canv 1 end] ;# convert "c2" to "2"
|
|
incr len 4
|
|
} else {
|
|
set canv ""
|
|
}
|
|
|
|
# services
|
|
set svc [getNodeServices $node false]
|
|
set svc [join $svc "|"]
|
|
set svc_len [string length $svc]
|
|
set svc_pad_len [pad_32bit $svc_len]
|
|
set svc_pad [binary format x$svc_pad_len]
|
|
if { $svc_len > 0 } { incr len [expr {2+$svc_len+$svc_pad_len } ] }
|
|
|
|
# icon
|
|
set icon [getCustomImage $node]
|
|
if { [file dirname $icon] == "$CORE_DATA_DIR/icons/normal" } {
|
|
set icon [file tail $icon] ;# don't include standard icon path
|
|
}
|
|
set icon_len [string length $icon]
|
|
set icon_pad_len [pad_32bit $icon_len]
|
|
set icon_pad [binary format x$icon_pad_len]
|
|
if { $icon_len > 0 } { incr len [expr {2+$icon_len+$icon_pad_len} ] }
|
|
|
|
# opaque data
|
|
set opaque_len [string length $opaque]
|
|
set opaque_pad_len [pad_32bit $opaque_len]
|
|
set opaque_pad [binary format x$opaque_pad_len]
|
|
if { $opaque_len > 0 } { incr len [expr {2+$opaque_len+$opaque_pad_len} ] }
|
|
|
|
# length must be calculated before this
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">NODE(flags=add/str,$node,type=$type,$name,"
|
|
}
|
|
set msg [binary format c2Sc2sIc2sIcc \
|
|
{0x1 0x11} $len \
|
|
{0x1 4} 0 $node_num \
|
|
{0x2 4} 0 $type \
|
|
0x3 $name_len ]
|
|
puts -nonewline $channel $msg$name$name_pad
|
|
|
|
# IPv4 address
|
|
if { $ipv4 > 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline "$ipv4str," }
|
|
set msg [binary format c2sI {0x4 4} 0 $ipv4]
|
|
puts -nonewline $channel $msg
|
|
}
|
|
|
|
# MAC address
|
|
if { $macstr != "" } {
|
|
if { $prmsg == 1 } { puts -nonewline "$macstr," }
|
|
set mac [join [split $macstr ":"] ""]
|
|
puts -nonewline $channel [binary format c2x2W {0x5 8} 0x$mac]
|
|
}
|
|
|
|
# IPv6 address
|
|
if { $ipv6 != 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline "$ipv6str," }
|
|
set msg [binary format c2 {0x6 16} ]
|
|
puts -nonewline $channel $msg
|
|
foreach ipv6w [split $ipv6 ":"] {
|
|
set msg [binary format S 0x$ipv6w]
|
|
puts -nonewline $channel $msg
|
|
}
|
|
puts -nonewline $channel [binary format x2]; # 2 bytes padding
|
|
}
|
|
|
|
# model type
|
|
if { $model_len > 0 } {
|
|
set mh [binary format cc 0x7 $model_len]
|
|
puts -nonewline $channel $mh$model$model_pad
|
|
if { $prmsg == 1 } { puts -nonewline "m=$model," }
|
|
}
|
|
|
|
# emulation server
|
|
if { $emusrv_len > 0 } {
|
|
puts -nonewline $channel [binary format cc 0x8 $emusrv_len]
|
|
puts -nonewline $channel $emusrv$emusrv_pad
|
|
if { $prmsg == 1 } { puts -nonewline "srv=$emusrv," }
|
|
}
|
|
|
|
# X,Y coordinates
|
|
set coords [getNodeCoords $node]
|
|
set x [format "%u" [expr int([lindex $coords 0])]]
|
|
set y [format "%u" [expr int([lindex $coords 1])]]
|
|
set msg [binary format c2Sc2S {0x20 2} $x {0x21 2} $y]
|
|
puts -nonewline $channel $msg
|
|
|
|
# canvas
|
|
if { $canv != "" } {
|
|
if { $prmsg == 1 } { puts -nonewline "canvas=$canv," }
|
|
set msg [binary format c2S {0x22 2} $canv]
|
|
puts -nonewline $channel $msg
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts -nonewline "x=$x,y=$y" }
|
|
|
|
# services
|
|
if { $svc_len > 0 } {
|
|
puts -nonewline $channel [binary format cc 0x25 $svc_len]
|
|
puts -nonewline $channel $svc$svc_pad
|
|
if { $prmsg == 1 } { puts -nonewline ",svc=$svc" }
|
|
}
|
|
|
|
# icon
|
|
if { $icon_len > 0 } {
|
|
puts -nonewline $channel [binary format cc 0x42 $icon_len]
|
|
puts -nonewline $channel $icon$icon_pad
|
|
if { $prmsg == 1 } { puts -nonewline ",icon=$icon" }
|
|
}
|
|
|
|
# opaque data
|
|
if { $opaque_len > 0 } {
|
|
puts -nonewline $channel [binary format cc 0x50 $opaque_len]
|
|
puts -nonewline $channel $opaque$opaque_pad
|
|
if { $prmsg == 1 } { puts -nonewline ",opaque=$opaque" }
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ")" }
|
|
|
|
flushChannel channel "Error sending node add"
|
|
}
|
|
|
|
# delete a node
|
|
proc sendNodeDelMessage { channel node } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
set len 8; # node number
|
|
set node_num [string range $node 1 end]
|
|
|
|
if { $prmsg == 1 } { puts ">NODE(flags=del/str,$node_num)" }
|
|
set msg [binary format c2Sc2sI \
|
|
{0x1 0x12} $len \
|
|
{0x1 4} 0 $node_num ]
|
|
puts -nonewline $channel $msg
|
|
flushChannel channel "Error sending node delete"
|
|
}
|
|
|
|
# send a message to build, modify, or delete a link
|
|
# type should indicate add/delete/link/unlink
|
|
proc sendLinkMessage { channel link type {sendboth true} } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
|
|
set node1 [lindex [linkPeers $link] 0]
|
|
set node2 [lindex [linkPeers $link] 1]
|
|
set if1 [ifcByPeer $node1 $node2]; set if2 [ifcByPeer $node2 $node1]
|
|
if { [nodeType $node1] == "pseudo" } { return } ;# never seems to occur
|
|
if { [nodeType $node2] == "pseudo" } {
|
|
set mirror2 [getLinkMirror $node2]
|
|
set node2 [getNodeName $node2]
|
|
if { [string range $node1 1 end] > [string range $node2 1 end] } {
|
|
return ;# only send one link message (for two pseudo-links)
|
|
}
|
|
set if2 [ifcByPeer $node2 $mirror2]
|
|
}
|
|
set node1_num [string range $node1 1 end]
|
|
set node2_num [string range $node2 1 end]
|
|
|
|
# flag for sending unidirectional link messages
|
|
set uni 0
|
|
if { $sendboth && [isLinkUni $link] } {
|
|
set uni 1
|
|
}
|
|
|
|
# set flags and link message type from supplied type parameter
|
|
set flags 0
|
|
set ltype 1 ;# add/delete a link (not wireless link/unlink)
|
|
set netid -1
|
|
if { $type == "add" || $type == "link" } {
|
|
set flags 1
|
|
} elseif { $type == "delete" || $type == "unlink" } {
|
|
set flags 2
|
|
}
|
|
if { $type == "link" || $type == "unlink" } {
|
|
set ltype 0 ;# a wireless link/unlink event
|
|
set tmp [getLinkOpaque $link net]
|
|
if { $tmp != "" } { set netid [string range $tmp 1 end] }
|
|
}
|
|
|
|
set key ""
|
|
if { [nodeType $node1] == "tunnel" } {
|
|
set key [netconfFetchSection $node1 "tunnel-key"]
|
|
if { $key == "" } { set key 1 }
|
|
}
|
|
if {[nodeType $node2] == "tunnel" } {
|
|
set key [netconfFetchSection $node2 "tunnel-key"]
|
|
if { $key == "" } { set key 1 }
|
|
}
|
|
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">LINK(flags=$flags,$node1_num-$node2_num,"
|
|
}
|
|
|
|
# len = node1num, node2num, type
|
|
set len [expr {8+8+8}]
|
|
set delay [getLinkDelay $link]
|
|
if { $delay == "" } { set delay 0 }
|
|
set jitter [getLinkJitter $link]
|
|
if { $jitter == "" } { set jitter 0 }
|
|
set bw [getLinkBandwidth $link]
|
|
if { $bw == "" } { set bw 0 }
|
|
set per [getLinkBER $link]; # PER and BER
|
|
if { $per == "" } { set per 0 }
|
|
set per_len 0
|
|
set per_msg [buildStringTLV 0x5 $per per_len]
|
|
set dup [getLinkDup $link]
|
|
if { $dup == "" } { set dup 0 }
|
|
set dup_len 0
|
|
set dup_msg [buildStringTLV 0x6 $dup dup_len]
|
|
if { $type != "delete" } {
|
|
incr len [expr {12+12+$per_len+$dup_len+12}] ;# delay,bw,per,dup,jitter
|
|
if {$prmsg==1 } {
|
|
puts -nonewline "$delay,$bw,$per,$dup,$jitter,"
|
|
}
|
|
}
|
|
# TODO: mer, burst, mburst
|
|
if { $prmsg == 1 } { puts -nonewline "type=$ltype," }
|
|
if { $uni } {
|
|
incr len 4
|
|
if { $prmsg == 1 } { puts -nonewline "uni=$uni," }
|
|
}
|
|
if { $netid > -1 } {
|
|
incr len 8
|
|
if { $prmsg == 1 } { puts -nonewline "netid=$netid," }
|
|
}
|
|
if { $key != "" } {
|
|
incr len 8
|
|
if { $prmsg == 1 } { puts -nonewline "key=$key," }
|
|
}
|
|
|
|
set if1num [ifcNameToNum $if1]; set if2num [ifcNameToNum $if2]
|
|
set if1ipv4 0; set if2ipv4 0; set if1ipv6 ""; set if2ipv6 "";
|
|
set if1ipv4mask 0; set if2ipv4mask 0;
|
|
set if1ipv6mask ""; set if2ipv6mask ""; set if1mac ""; set if2mac "";
|
|
|
|
if { $if1num >= 0 && ([[typemodel $node1].layer] == "NETWORK" || \
|
|
[nodeType $node1] == "tunnel") } {
|
|
incr len 4
|
|
if { $prmsg == 1 } { puts -nonewline "if1n=$if1num," }
|
|
if { $type != "delete" } {
|
|
getIfcAddrs $node1 $if1 if1ipv4 if1ipv6 if1mac if1ipv4mask \
|
|
if1ipv6mask len
|
|
}
|
|
}
|
|
if { $if2num >= 0 && ([[typemodel $node2].layer] == "NETWORK" || \
|
|
[nodeType $node2] == "tunnel") } {
|
|
incr len 4
|
|
if { $prmsg == 1 } { puts -nonewline "if2n=$if2num," }
|
|
if { $type != "delete" } {
|
|
getIfcAddrs $node2 $if2 if2ipv4 if2ipv6 if2mac if2ipv4mask \
|
|
if2ipv6mask len
|
|
}
|
|
}
|
|
|
|
# start building the binary message on channel
|
|
# length must be calculated before this
|
|
set msg [binary format ccSc2sIc2sI \
|
|
{0x2} $flags $len \
|
|
{0x1 4} 0 $node1_num \
|
|
{0x2 4} 0 $node2_num ]
|
|
puts -nonewline $channel $msg
|
|
|
|
if { $type != "delete" } {
|
|
puts -nonewline $channel [binary format c2sW {0x3 8} 0 $delay]
|
|
puts -nonewline $channel [binary format c2sW {0x4 8} 0 $bw]
|
|
puts -nonewline $channel $per_msg
|
|
puts -nonewline $channel $dup_msg
|
|
puts -nonewline $channel [binary format c2sW {0x7 8} 0 $jitter]
|
|
}
|
|
# TODO: mer, burst, mburst
|
|
|
|
# link type
|
|
puts -nonewline $channel [binary format c2sI {0x20 4} 0 $ltype]
|
|
|
|
# unidirectional flag
|
|
if { $uni } {
|
|
puts -nonewline $channel [binary format c2S {0x22 2} $uni]
|
|
}
|
|
|
|
# network ID
|
|
if { $netid > -1 } {
|
|
puts -nonewline $channel [binary format c2sI {0x24 4} 0 $netid]
|
|
}
|
|
|
|
if { $key != "" } {
|
|
puts -nonewline $channel [binary format c2sI {0x25 4} 0 $key]
|
|
}
|
|
|
|
# interface 1 info
|
|
if { $if1num >= 0 && ([[typemodel $node1].layer] == "NETWORK" || \
|
|
[nodeType $node1] == "tunnel") } {
|
|
puts -nonewline $channel [ binary format c2S {0x30 2} $if1num ]
|
|
}
|
|
if { $if1ipv4 > 0 } { puts -nonewline $channel [binary format c2sIc2S \
|
|
{0x31 4} 0 $if1ipv4 {0x32 2} $if1ipv4mask ] }
|
|
if { $if1mac != "" } {
|
|
set if1mac [join [split $if1mac ":"] ""]
|
|
puts -nonewline $channel [binary format c2x2W {0x33 8} 0x$if1mac]
|
|
}
|
|
if {$if1ipv6 != ""} { puts -nonewline $channel [binary format c2 {0x34 16}]
|
|
foreach ipv6w [split $if1ipv6 ":"] { puts -nonewline $channel \
|
|
[binary format S 0x$ipv6w] }
|
|
puts -nonewline $channel [binary format x2c2S {0x35 2} $if1ipv6mask] }
|
|
|
|
# interface 2 info
|
|
if { $if2num >= 0 && ([[typemodel $node2].layer] == "NETWORK" || \
|
|
[nodeType $node2] == "tunnel") } {
|
|
puts -nonewline $channel [ binary format c2S {0x36 2} $if2num ]
|
|
}
|
|
if { $if2ipv4 > 0 } { puts -nonewline $channel [binary format c2sIc2S \
|
|
{0x37 4} 0 $if2ipv4 {0x38 2} $if2ipv4mask ] }
|
|
if { $if2mac != "" } {
|
|
set if2mac [join [split $if2mac ":"] ""]
|
|
puts -nonewline $channel [binary format c2x2W {0x39 8} 0x$if2mac]
|
|
}
|
|
if {$if2ipv6 != ""} { puts -nonewline $channel [binary format c2 {0x40 16}]
|
|
foreach ipv6w [split $if2ipv6 ":"] { puts -nonewline $channel \
|
|
[binary format S 0x$ipv6w] }
|
|
puts -nonewline $channel [binary format x2c2S {0x41 2} $if2ipv6mask] }
|
|
|
|
if { $prmsg==1 } { puts ")" }
|
|
flushChannel channel "Error sending link message"
|
|
|
|
##########################################################
|
|
# send a second Link Message for unidirectional link effects
|
|
if { $uni < 1 } {
|
|
return
|
|
}
|
|
# first calculate length and possibly print the message
|
|
set flags 0
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">LINK(flags=$flags,$node2_num-$node1_num,"
|
|
}
|
|
set len [expr {8+8+8}] ;# len = node2num, node1num (swapped), type
|
|
set delay [getLinkDelay $link up]
|
|
if { $delay == "" } { set delay 0 }
|
|
set jitter [getLinkJitter $link up]
|
|
if { $jitter == "" } { set jitter 0 }
|
|
set bw [getLinkBandwidth $link up]
|
|
if { $bw == "" } { set bw 0 }
|
|
set per [getLinkBER $link up]; # PER and BER
|
|
if { $per == "" } { set per 0 }
|
|
set per_len 0
|
|
set per_msg [buildStringTLV 0x5 $per per_len]
|
|
set dup [getLinkDup $link up]
|
|
if { $dup == "" } { set dup 0 }
|
|
set dup_len 0
|
|
set dup_msg [buildStringTLV 0x6 $dup dup_len]
|
|
incr len [expr {12+12+$per_len+$dup_len+12}] ;# delay,bw,per,dup,jitter
|
|
if {$prmsg==1 } {
|
|
puts -nonewline "$delay,$bw,$per,$dup,$jitter,"
|
|
}
|
|
if { $prmsg == 1 } { puts -nonewline "type=$ltype," }
|
|
incr len 4 ;# unidirectional flag
|
|
if { $prmsg == 1 } { puts -nonewline "uni=$uni," }
|
|
# note that if1num / if2num are reversed here due to reversed node nums
|
|
if { $if2num >= 0 && ([[typemodel $node2].layer] == "NETWORK" || \
|
|
[nodeType $node2] == "tunnel") } {
|
|
incr len 4
|
|
if { $prmsg == 1 } { puts -nonewline "if1n=$if2num," }
|
|
}
|
|
if { $if1num >= 0 && ([[typemodel $node1].layer] == "NETWORK" || \
|
|
[nodeType $node1] == "tunnel") } {
|
|
incr len 4
|
|
if { $prmsg == 1 } { puts -nonewline "if2n=$if1num," }
|
|
}
|
|
# build and send the link message
|
|
set msg [binary format ccSc2sIc2sI \
|
|
{0x2} $flags $len \
|
|
{0x1 4} 0 $node2_num \
|
|
{0x2 4} 0 $node1_num ]
|
|
puts -nonewline $channel $msg
|
|
puts -nonewline $channel [binary format c2sW {0x3 8} 0 $delay]
|
|
puts -nonewline $channel [binary format c2sW {0x4 8} 0 $bw]
|
|
puts -nonewline $channel $per_msg
|
|
puts -nonewline $channel $dup_msg
|
|
puts -nonewline $channel [binary format c2sW {0x7 8} 0 $jitter]
|
|
puts -nonewline $channel [binary format c2sI {0x20 4} 0 $ltype]
|
|
puts -nonewline $channel [binary format c2S {0x22 2} $uni]
|
|
if { $if2num >= 0 && ([[typemodel $node2].layer] == "NETWORK" || \
|
|
[nodeType $node2] == "tunnel") } {
|
|
puts -nonewline $channel [ binary format c2S {0x30 2} $if2num ]
|
|
}
|
|
if { $if1num >= 0 && ([[typemodel $node1].layer] == "NETWORK" || \
|
|
[nodeType $node1] == "tunnel") } {
|
|
puts -nonewline $channel [ binary format c2S {0x36 2} $if1num ]
|
|
}
|
|
if { $prmsg==1 } { puts ")" }
|
|
flushChannel channel "Error sending link message"
|
|
}
|
|
|
|
# helper to get IPv4, IPv6, MAC address and increment length
|
|
# also prints TLV-style addresses if showAPI is true
|
|
proc getIfcAddrs { node ifc ipv4p ipv6p macp ipv4maskp ipv6maskp lenp } {
|
|
global showAPI
|
|
upvar $ipv4p ipv4
|
|
upvar $ipv6p ipv6
|
|
upvar $macp mac
|
|
upvar $ipv4maskp ipv4mask
|
|
upvar $ipv6maskp ipv6mask
|
|
upvar $lenp len
|
|
|
|
if { $ifc == "" || $node == "" } { return }
|
|
|
|
# IPv4 address
|
|
set ipv4str [getIfcIPv4addr $node $ifc]
|
|
if {$ipv4str != ""} {
|
|
set ipv4 [lindex [split $ipv4str /] 0]
|
|
if { [info exists ipv4mask ] } {
|
|
set ipv4mask [lindex [split $ipv4str / ] 1]
|
|
incr len 12; # 8 addr + 4 mask
|
|
if { $showAPI == 1 } { puts -nonewline "$ipv4str," }
|
|
} else {
|
|
incr len 8; # 8 addr
|
|
if { $showAPI == 1 } { puts -nonewline "$ipv4," }
|
|
}
|
|
set ipv4 [stringToIPv4 $ipv4]; # convert to integer
|
|
}
|
|
|
|
# IPv6 address
|
|
set ipv6str [getIfcIPv6addr $node $ifc]
|
|
if {$ipv6str != ""} {
|
|
set ipv6 [lindex [split $ipv6str /] 0]
|
|
if { [info exists ipv6mask ] } {
|
|
set ipv6mask [lindex [split $ipv6str / ] 1]
|
|
incr len 24; # 20 addr + 4 mask
|
|
if { $showAPI == 1 } { puts -nonewline "$ipv6str," }
|
|
} else {
|
|
incr len 20; # 20 addr
|
|
if { $showAPI == 1 } { puts -nonewline "$ipv6," }
|
|
}
|
|
set ipv6 [expandIPv6 $ipv6]; # convert to long string
|
|
}
|
|
|
|
# MAC address (from conf if there, otherwise generated)
|
|
if { [info exists mac] } {
|
|
set mac [lindex [getIfcMacaddr $node $ifc] 0]
|
|
if {$mac == ""} {
|
|
set mac [getNextMac]
|
|
}
|
|
if { $showAPI == 1 } { puts -nonewline "$mac," }
|
|
incr len 12;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Register Message: (registration types)
|
|
# This is a simple Register Message, types is an array of
|
|
# <module TLV, string> tuples.
|
|
proc sendRegMessage { channel flags types_list } {
|
|
global showAPI regtypes
|
|
set prmsg $showAPI
|
|
|
|
if { $channel == -1 || $channel == "" } {
|
|
set plugin [lindex [getEmulPlugin "*"] 0]
|
|
set channel [pluginConnect $plugin connect true]
|
|
if { $channel == -1 } { return }
|
|
}
|
|
set len 0
|
|
array set types $types_list
|
|
|
|
# array names output is unreliable, sort it
|
|
set type_list [lsort -dict [array names types]]
|
|
foreach type $type_list {
|
|
if { ![info exists regtypes($type)] } {
|
|
puts "sendRegMessage: unknown registration type '$type'"
|
|
return -1
|
|
}
|
|
set str_$type $types($type)
|
|
set str_${type}_len [string length [set str_$type]]
|
|
set str_${type}_pad_len [pad_32bit [set str_${type}_len]]
|
|
set str_${type}_pad [binary format x[set str_${type}_pad_len]]
|
|
incr len [expr { 2 + [set str_${type}_len] + [set str_${type}_pad_len]}]
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ">REG($type_list)" }
|
|
# message header
|
|
set msg1 [binary format ccS 4 $flags $len]
|
|
puts -nonewline $channel $msg1
|
|
|
|
foreach type $type_list {
|
|
set type_num $regtypes($type)
|
|
set tlvh [binary format cc $type_num [set str_${type}_len]]
|
|
puts -nonewline $channel $tlvh[set str_${type}][set str_${type}_pad]
|
|
}
|
|
|
|
flushChannel channel "Error: API channel was closed"
|
|
}
|
|
|
|
#
|
|
# Configuration Message: (object, type flags, node)
|
|
# This is a simple Configuration Message containing flags
|
|
proc sendConfRequestMessage { channel node model flags netid opaque } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
|
|
if { $channel == -1 || $channel == "" } {
|
|
set pname [lindex [getEmulPlugin $node] 0]
|
|
set channel [pluginConnect $pname connect true]
|
|
if { $channel == -1 } { return }
|
|
}
|
|
|
|
set model_len [string length $model]
|
|
set model_pad_len [pad_32bit $model_len]
|
|
set model_pad [binary format x$model_pad_len ]
|
|
set len [expr {4+2+$model_len+$model_pad_len}]
|
|
# optional network ID to provide Netgraph mapping
|
|
if { $netid != -1 } { incr len 8 }
|
|
# convert from node name to number
|
|
if { [string is alpha [string range $node 0 0]] } {
|
|
set node [string range $node 1 end]
|
|
}
|
|
|
|
if { $node > 0 } { incr len 8 }
|
|
# add a session number when configuring services
|
|
set session ""
|
|
set session_len 0
|
|
set session_pad_len 0
|
|
set session_pad ""
|
|
if { $node <= 0 && $model == "services" } {
|
|
global g_current_session
|
|
set session [format "0x%x" $g_current_session]
|
|
set session_len [string length $session]
|
|
set session_pad_len [pad_32bit $session_len]
|
|
set session_pad [binary format x$session_pad_len]
|
|
incr len [expr {2 + $session_len + $session_pad_len}]
|
|
}
|
|
# opaque data - used when custom configuring services
|
|
set opaque_len 0
|
|
set msgop [buildStringTLV 0x50 $opaque opaque_len]
|
|
if { $opaque_len > 0 } { incr len $opaque_len }
|
|
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">CONF(flags=0,"
|
|
if { $node > 0 } { puts -nonewline "node=$node," }
|
|
puts -nonewline "obj=$model,cflags=$flags"
|
|
if { $session != "" } { puts -nonewline ",session=$session" }
|
|
if { $netid > -1 } { puts -nonewline ",netid=$netid" }
|
|
if { $opaque_len > 0 } { puts -nonewline ",opaque=$opaque" }
|
|
puts ") request"
|
|
}
|
|
# header, node node number, node model header
|
|
set msg1 [binary format c2S {5 0} $len ]
|
|
set msg1b ""
|
|
if { $node > 0 } { set msg1b [binary format c2sI {1 4} 0 $node] }
|
|
set msg1c [binary format cc 2 $model_len]
|
|
# request flag
|
|
set msg2 [binary format c2S {3 2} $flags ]
|
|
# session number
|
|
set msg3 ""
|
|
if { $session != "" } {
|
|
set msg3 [binary format cc 0x0A $session_len]
|
|
set msg3 $msg3$session$session_pad
|
|
}
|
|
# network ID
|
|
set msg4 ""
|
|
if { $netid != -1 } {
|
|
set msg4 [binary format c2sI {0x23 4} 0 0x$netid ]
|
|
}
|
|
|
|
#catch {puts -nonewline $channel $msg1$model$model_pad$msg2$msg3$msg4$msg5}
|
|
puts -nonewline $channel $msg1$msg1b$msg1c$model$model_pad$msg2$msg3$msg4
|
|
if { $opaque_len > 0 } { puts -nonewline $channel $msgop }
|
|
|
|
flushChannel channel "Error: API channel was closed"
|
|
}
|
|
|
|
#
|
|
# Configuration Message: (object, type flags, node, types, values)
|
|
# This message is more complicated to build because of the list of
|
|
# data types and values.
|
|
proc sendConfReplyMessage { channel node model types values opaque } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
# convert from node name to number
|
|
if { [string is alpha [string range $node 0 0]] } {
|
|
set node [string range $node 1 end]
|
|
}
|
|
# add a session number when configuring services
|
|
set session ""
|
|
set session_len 0
|
|
set session_pad_len 0
|
|
set session_pad ""
|
|
if { $node <= 0 && $model == "services" && $opaque == "" } {
|
|
global g_current_session
|
|
set session [format "0x%x" $g_current_session]
|
|
set session_len [string length $session]
|
|
set session_pad_len [pad_32bit $session_len]
|
|
set session_pad [binary format x$session_pad_len]
|
|
incr len [expr {$session_len + $session_pad_len}]
|
|
}
|
|
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">CONF(flags=0,"
|
|
if {$node > -1 } { puts -nonewline "node=$node," }
|
|
puts -nonewline "obj=$model,cflags=0"
|
|
if {$session != "" } { puts -nonewline "session=$session," }
|
|
if {$opaque != "" } { puts -nonewline "opaque=$opaque," }
|
|
puts "types=<$types>,values=<$values>) reply"
|
|
}
|
|
|
|
# types (16-bit values) and values
|
|
set n 0
|
|
set type_len [expr {[llength $types] * 2} ]
|
|
set type_data [binary format cc 4 $type_len]
|
|
set value_data ""
|
|
foreach type $types {
|
|
set t [binary format S $type]
|
|
set type_data $type_data$t
|
|
set val [lindex $values $n]
|
|
if { $val == "" } {
|
|
#puts "warning: empty value $n (type=$type)"
|
|
if { $type != 10 } { set val 0 }
|
|
}
|
|
incr n
|
|
lappend value_data $val
|
|
}; # end foreach
|
|
set value_len 0
|
|
set value_data [join $value_data |]
|
|
set msgval [buildStringTLV 0x5 $value_data value_len]
|
|
set type_pad_len [pad_32bit $type_len]
|
|
set type_pad [binary format x$type_pad_len ]
|
|
set model_len [string length $model]
|
|
set model_pad_len [pad_32bit $model_len]
|
|
set model_pad [binary format x$model_pad_len ]
|
|
# opaque data - used when custom configuring services
|
|
set opaque_len 0
|
|
set msgop [buildStringTLV 0x50 $opaque opaque_len]
|
|
|
|
# 4 bytes header, model TLV
|
|
set len [expr 4+2+$model_len+$model_pad_len]
|
|
if { $node > -1 } { incr len 8 }
|
|
# session number
|
|
set msg3 ""
|
|
if { $session != "" } {
|
|
incr len [expr {2 + $session_len + $session_pad_len }]
|
|
set msg3 [binary format cc 0x0A $session_len]
|
|
set msg3 $msg3$session$session_pad
|
|
}
|
|
if { $opaque_len > 0 } { incr len $opaque_len }
|
|
# types TLV, values TLV
|
|
incr len [expr {2 + $type_len + $type_pad_len + $value_len}]
|
|
|
|
# header, node node number, node model header
|
|
set msgh [binary format c2S {5 0} $len ]
|
|
set msgwl ""
|
|
if { $node > -1 } { set msgwl [binary format c2sI {1 4} 0 $node] }
|
|
set model_hdr [binary format cc 2 $model_len]
|
|
# no flags
|
|
set type_hdr [binary format c2S {3 2} 0 ]
|
|
set msg $msgh$msgwl$model_hdr$model$model_pad$type_hdr$type_data$type_pad
|
|
set msg $msg$msgval$msg3
|
|
puts -nonewline $channel $msg
|
|
if { $opaque_len > 0 } { puts -nonewline $channel $msgop }
|
|
flushChannel channel "Error sending conf reply"
|
|
}
|
|
|
|
# Event Message
|
|
proc sendEventMessage { channel type nodenum name data flags } {
|
|
global showAPI eventtypes
|
|
set prmsg $showAPI
|
|
|
|
set len [expr 8] ;# event type
|
|
if {$nodenum > -1} { incr len 8 }
|
|
set name_len [string length $name]
|
|
set name_pad_len [pad_32bit $name_len]
|
|
if { $name_len > 0 } { incr len [expr {2 + $name_len + $name_pad_len}] }
|
|
set data_len [string length $data]
|
|
set data_pad_len [pad_32bit $data_len]
|
|
if { $data_len > 0 } { incr len [expr {2 + $data_len + $data_pad_len}] }
|
|
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">EVENT(flags=$flags," }
|
|
set msg [binary format ccS 8 $flags $len ] ;# message header
|
|
|
|
set msg2 ""
|
|
if { $nodenum > -1 } {
|
|
if { $prmsg == 1 } { puts -nonewline "node=$nodenum," }
|
|
set msg2 [binary format c2sI {0x01 4} 0 $nodenum]
|
|
}
|
|
if { $prmsg == 1} {
|
|
set typestr ""
|
|
foreach t [array names eventtypes] {
|
|
if { $eventtypes($t) == $type } { set typestr "-$t"; break }
|
|
}
|
|
puts -nonewline "type=$type$typestr,"
|
|
}
|
|
set msg3 [binary format c2sI {0x02 4} 0 $type]
|
|
set msg4 ""
|
|
set msg5 ""
|
|
if { $name_len > 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline "name=$name," }
|
|
set msg4 [binary format cc 0x03 $name_len ]
|
|
set name_pad [binary format x$name_pad_len ]
|
|
set msg5 $name$name_pad
|
|
}
|
|
set msg6 ""
|
|
set msg7 ""
|
|
if { $data_len > 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline "data=$data" }
|
|
set msg6 [binary format cc 0x04 $data_len ]
|
|
set data_pad [binary format x$data_pad_len ]
|
|
set msg7 $data$data_pad
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ")" }
|
|
puts -nonewline $channel $msg$msg2$msg3$msg4$msg5$msg6$msg7
|
|
flushChannel channel "Error sending Event type=$type"
|
|
}
|
|
|
|
|
|
# deploy working configuration using CORE API
|
|
# Deploys a current working configuration. It creates all the
|
|
# nodes and link as defined in configuration file.
|
|
proc deployCfgAPI { sock } {
|
|
global eid
|
|
global node_list link_list annotation_list canvas_list
|
|
global mac_byte4 mac_byte5
|
|
global execMode
|
|
global ngnodemap
|
|
global mac_addr_start
|
|
global deployCfgAPI_lock
|
|
global eventtypes
|
|
global g_comments
|
|
|
|
if { ![info exists deployCfgAPI_lock] } { set deployCfgAPI_lock 0 }
|
|
if { $deployCfgAPI_lock } {
|
|
puts "***error: deployCfgAPI called while deploying config"
|
|
return
|
|
}
|
|
|
|
set deployCfgAPI_lock 1 ;# lock
|
|
|
|
set mac_byte4 0
|
|
set mac_byte5 0
|
|
if { [info exists mac_addr_start] } { set mac_byte5 $mac_addr_start }
|
|
set t_start [clock seconds]
|
|
|
|
global systype
|
|
set systype [lindex [checkOS] 0]
|
|
statgraph on [expr (2*[llength $node_list]) + [llength $link_list]]
|
|
|
|
|
|
sendSessionProperties $sock
|
|
|
|
# this tells the CORE services that we are starting to send
|
|
# configuration data
|
|
# clear any existing config
|
|
sendEventMessage $sock $eventtypes(definition_state) -1 "" "" 0
|
|
# inform CORE services about emulation servers, hook scripts, canvas info,
|
|
# and services
|
|
sendEventMessage $sock $eventtypes(configuration_state) -1 "" "" 0
|
|
sendEmulationServerInfo $sock 0
|
|
sendSessionOptions $sock
|
|
sendHooks $sock
|
|
sendCanvasInfo $sock
|
|
sendNodeTypeInfo $sock 0
|
|
# send any custom service info before the node messages
|
|
sendNodeCustomServices $sock
|
|
|
|
# send Node add messages for all emulation nodes
|
|
foreach node $node_list {
|
|
set node_id "$eid\_$node"
|
|
set type [nodeType $node]
|
|
set name [getNodeName $node]
|
|
if { $type == "pseudo" } { continue }
|
|
|
|
statgraph inc 1
|
|
statline "Creating node $name"
|
|
if { [[typemodel $node].layer] == "NETWORK" } {
|
|
nodeHighlights .c $node on red
|
|
}
|
|
# inform the CORE daemon of the node
|
|
sendNodeAddMessage $sock $node
|
|
pluginCapsInitialize $node "mobmodel"
|
|
writeNodeCoords $node [getNodeCoords $node]
|
|
}
|
|
|
|
# send Link add messages for all network links
|
|
for { set pending_links $link_list } { $pending_links != "" } {} {
|
|
set link [lindex $pending_links 0]
|
|
set i [lsearch -exact $pending_links $link]
|
|
set pending_links [lreplace $pending_links $i $i]
|
|
statgraph inc 1
|
|
|
|
set lnode1 [lindex [linkPeers $link] 0]
|
|
set lnode2 [lindex [linkPeers $link] 1]
|
|
if { [nodeType $lnode2] == "router" && \
|
|
[getNodeModel $lnode2] == "remote" } {
|
|
continue; # remote routers are ctrl. by GUI; TODO: move to daemon
|
|
}
|
|
sendLinkMessage $sock $link add
|
|
}
|
|
|
|
# GUI-specific meta-data send via Configure Messages
|
|
if { [llength $annotation_list] > 0 } {
|
|
sendMetaData $sock $annotation_list "annotation"
|
|
}
|
|
sendMetaData $sock $canvas_list "canvas" ;# assume >= 1 canvas
|
|
# global GUI options - send as meta-data
|
|
set obj "metadata"
|
|
set values [getGlobalOptionList]
|
|
sendConfReplyMessage $sock -1 $obj "10" "{global_options=$values}" ""
|
|
if { [info exists g_comments] && $g_comments != "" } {
|
|
sendConfReplyMessage $sock -1 $obj "10" "{comments=$g_comments}" ""
|
|
}
|
|
|
|
# status bar graph
|
|
statgraph off 0
|
|
statline "Network topology instantiated in [expr [clock seconds] - $t_start] seconds ([llength $node_list] nodes and [llength $link_list] links)."
|
|
|
|
# TODO: turn on tcpdump if enabled; customPostConfigCommands;
|
|
# addons 4 deployCfgHook
|
|
|
|
# draw lines between wlan nodes
|
|
# initialization does not work earlier than this
|
|
|
|
foreach node $node_list {
|
|
# WLAN handling: draw lines between wireless nodes
|
|
if { [nodeType $node] == "wlan" && $execMode == "interactive" } {
|
|
wlanRunMobilityScript $node
|
|
}
|
|
}
|
|
|
|
sendTrafficScripts $sock
|
|
|
|
# tell the CORE services that we are ready to instantiate
|
|
sendEventMessage $sock $eventtypes(instantiation_state) -1 "" "" 0
|
|
|
|
set deployCfgAPI_lock 0 ;# unlock
|
|
|
|
statline "Network topology instantiated in [expr [clock seconds] - $t_start] seconds ([llength $node_list] nodes and [llength $link_list] links)."
|
|
}
|
|
|
|
#
|
|
# emulation shutdown procedure when using the CORE API
|
|
proc shutdownSession {} {
|
|
global link_list node_list eid eventtypes execMode
|
|
|
|
set nodecount [getNodeCount]
|
|
if { $nodecount == 0 } {
|
|
# This allows switching to edit mode without extra API messages,
|
|
# such as when file new is selected while running an existing session.
|
|
return
|
|
}
|
|
|
|
# prepare the channel
|
|
set plugin [lindex [getEmulPlugin "*"] 0]
|
|
set sock [pluginConnect $plugin connect true]
|
|
|
|
sendEventMessage $sock $eventtypes(datacollect_state) -1 "" "" 0
|
|
|
|
# shut down all links
|
|
foreach link $link_list {
|
|
|
|
set lnode2 [lindex [linkPeers $link] 1]
|
|
if { [nodeType $lnode2] == "router" && \
|
|
[getNodeModel $lnode2] == "remote" } {
|
|
continue; # remote routers are ctrl. by GUI; TODO: move to daemon
|
|
}
|
|
|
|
sendLinkMessage $sock $link delete false
|
|
}
|
|
# shut down all nodes
|
|
foreach node $node_list {
|
|
set type [nodeType $node]
|
|
if { [[typemodel $node].layer] == "NETWORK" && $execMode != "batch" } {
|
|
nodeHighlights .c $node on red
|
|
}
|
|
sendNodeDelMessage $sock $node
|
|
pluginCapsDeinitialize $node "mobmodel"
|
|
deleteNodeCoords $node
|
|
}
|
|
|
|
sendNodeTypeInfo $sock 1
|
|
sendEmulationServerInfo $sock 1
|
|
}
|
|
|
|
# inform the CORE services about the canvas information to support
|
|
# conversion between X,Y and lat/long coordinates
|
|
proc sendCanvasInfo { sock } {
|
|
global curcanvas
|
|
|
|
if { ![info exists curcanvas] } { return } ;# batch mode
|
|
set obj "location"
|
|
|
|
set scale [getCanvasScale $curcanvas]
|
|
set refpt [getCanvasRefPoint $curcanvas]
|
|
set refx [lindex $refpt 0]
|
|
set refy [lindex $refpt 1]
|
|
set latitude [lindex $refpt 2]
|
|
set longitude [lindex $refpt 3]
|
|
set altitude [lindex $refpt 4]
|
|
|
|
set types [list 2 2 10 10 10 10]
|
|
set values [list $refx $refy $latitude $longitude $altitude $scale]
|
|
|
|
sendConfReplyMessage $sock -1 $obj $types $values ""
|
|
}
|
|
|
|
# inform the CORE services about the default services for a node type, which
|
|
# are used when node-specific services have not been configured for a node
|
|
proc sendNodeTypeInfo { sock reset } {
|
|
global node_list
|
|
|
|
set obj "services"
|
|
|
|
if { $reset == 1} {
|
|
sendConfRequestMessage $sock -1 "all" 0x3 -1 ""
|
|
return
|
|
}
|
|
# build a list of node types in use
|
|
set typesinuse ""
|
|
foreach node $node_list {
|
|
set type [nodeType $node]
|
|
if { $type != "router" } { continue }
|
|
set model [getNodeModel $node]
|
|
if { [lsearch $typesinuse $model] < 0 } { lappend typesinuse $model }
|
|
}
|
|
|
|
foreach type $typesinuse {
|
|
# build a list of type + enabled services, all strings
|
|
set values [getNodeTypeServices $type]
|
|
set values [linsert $values 0 $type]
|
|
set types [string repeat "10 " [llength $values]]
|
|
sendConfReplyMessage $sock -1 $obj $types $values ""
|
|
# send any custom profiles for a node type; node type passed in opaque
|
|
set machine_type [getNodeTypeMachineType $type]
|
|
set values [getNodeTypeProfile $type]
|
|
if { $values != "" } {
|
|
set types [string repeat "10 " [llength $values]]
|
|
sendConfReplyMessage $sock -1 $machine_type $types $values \
|
|
"$machine_type:$type"
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# inform the CORE services about any services that have been customized for
|
|
# a particular node
|
|
proc sendNodeCustomServices { sock } {
|
|
global node_list
|
|
foreach node $node_list {
|
|
set cfgs [getCustomConfig $node]
|
|
set cfgfiles ""
|
|
foreach cfg $cfgs {
|
|
set ids [split [getConfig $cfg "custom-config-id"] :]
|
|
if { [lindex $ids 0] != "service" } { continue }
|
|
if { [llength $ids] == 3 } {
|
|
# customized service config file -- build a list
|
|
lappend cfgfiles $cfg
|
|
continue
|
|
}
|
|
set s [lindex $ids 1]
|
|
set values [getConfig $cfg "config"]
|
|
set t [string repeat "10 " [llength $values]]
|
|
sendConfReplyMessage $sock $node services $t $values "service:$s"
|
|
}
|
|
# send customized service config files after the service info
|
|
foreach cfg $cfgfiles {
|
|
set idstr [getConfig $cfg "custom-config-id"]
|
|
set ids [split $idstr :]
|
|
if { [lindex $ids 0] != "service" } { continue }
|
|
set s [lindex $ids 1]
|
|
set filename [lindex $ids 2]
|
|
set data [join [getConfig $cfg "config"] "\n"]
|
|
sendFileMessage $sock $node "service:$s" $filename "" $data \
|
|
[string length $data]
|
|
}
|
|
}
|
|
}
|
|
|
|
# publish hooks to the CORE services
|
|
proc sendHooks { sock } {
|
|
global g_hook_scripts
|
|
if { ![info exists g_hook_scripts] } { return }
|
|
foreach hook $g_hook_scripts {
|
|
set name [lindex $hook 0]
|
|
set state [lindex $hook 1]
|
|
set data [lindex $hook 2]
|
|
# TODO: modify sendFileMessage to make node number optional
|
|
sendFileMessage $sock n0 "hook:$state" $name "" $data \
|
|
[string length $data]
|
|
}
|
|
}
|
|
|
|
# inform the CORE services about the emulation servers that will be used
|
|
proc sendEmulationServerInfo { sock reset } {
|
|
global exec_servers
|
|
set node -1 ;# not used
|
|
set obj "broker"
|
|
|
|
set servernames [getAssignedRemoteServers]
|
|
if { $servernames == "" } { return } ;# not using emulation servers
|
|
|
|
if { $reset == 1} {
|
|
sendConfRequestMessage $sock $node $obj 0x3 -1 ""
|
|
return
|
|
}
|
|
|
|
set servers ""
|
|
foreach servername $servernames {
|
|
set host [lindex $exec_servers($servername) 0]
|
|
set port [lindex $exec_servers($servername) 1]
|
|
lappend servers "$servername:$host:$port"
|
|
}
|
|
|
|
set serversstring [join $servers ,]
|
|
|
|
set types [list 10]
|
|
set values [list $serversstring]
|
|
|
|
sendConfReplyMessage $sock $node $obj $types $values ""
|
|
}
|
|
|
|
# returns the length of node_list minus any pseudo-nodes (inter-canvas nodes)
|
|
proc getNodeCount {} {
|
|
global node_list
|
|
set nodecount 0
|
|
foreach node $node_list {
|
|
if { [nodeType $node] != "pseudo" } { incr nodecount }
|
|
}
|
|
return $nodecount
|
|
}
|
|
|
|
# send basic properties of a session
|
|
proc sendSessionProperties { sock } {
|
|
global currentFile CORE_DATA_DIR CORE_USER
|
|
set sessionname [file tail $currentFile]
|
|
set nodecount [getNodeCount]
|
|
if { $sessionname == "" } { set sessionname "untitled" }
|
|
set tf "/tmp/thumb.jpg"
|
|
if { ![writeCanvasThumbnail .c $tf] } {
|
|
set src "$CORE_DATA_DIR/icons/normal/thumb-unknown.gif"
|
|
set tf "/tmp/thumb.gif"
|
|
if [catch { file copy $src $tf } e] {
|
|
puts -nonewline "warning: failed to copy $src to $tf\n($e)"
|
|
set tf ""
|
|
}
|
|
}
|
|
set user $CORE_USER
|
|
sendSessionMessage $sock 0 0 $sessionname $currentFile $nodecount $tf $user
|
|
}
|
|
|
|
# send session options from global array in Config Message
|
|
proc sendSessionOptions { sock } {
|
|
if { $sock == -1 } {
|
|
set sock [lindex [getEmulPlugin "*"] 2]
|
|
}
|
|
set values [getSessionOptionsList]
|
|
set types [string repeat "10 " [llength $values]]
|
|
sendConfReplyMessage $sock -1 "session" $types $values ""
|
|
}
|
|
|
|
# send annotations as key=value metadata in Config Message
|
|
proc sendAnnotations { sock } {
|
|
global annotation_list
|
|
|
|
if { $sock == -1 } {
|
|
set sock [lindex [getEmulPlugin "*"] 2]
|
|
}
|
|
set values ""
|
|
foreach a $annotation_list {
|
|
global $a
|
|
set val [set $a]
|
|
lappend values "annotation $a=$val"
|
|
}
|
|
set types [string repeat "10 " [llength $values]]
|
|
sendConfReplyMessage $sock -1 "metadata" $types $values ""
|
|
}
|
|
|
|
# send items as key=value metadata in Config Message
|
|
proc sendMetaData { sock items itemtype } {
|
|
|
|
if { $sock == -1 } {
|
|
set sock [lindex [getEmulPlugin "*"] 2]
|
|
}
|
|
set values ""
|
|
foreach i $items {
|
|
global $i
|
|
set val [set $i]
|
|
lappend values "$itemtype $i=$val"
|
|
}
|
|
set types [string repeat "10 " [llength $values]]
|
|
sendConfReplyMessage $sock -1 "metadata" $types $values ""
|
|
}
|
|
|
|
# send an Event message for the definition state (this clears any existing
|
|
# state), then send all node and link definitions to the CORE services
|
|
proc sendNodeLinkDefinitions { sock } {
|
|
global node_list link_list annotation_list canvas_list eventtypes
|
|
global g_comments
|
|
#sendEventMessage $sock $eventtypes(definition_state) -1 "" "" 0
|
|
foreach node $node_list {
|
|
sendNodeAddMessage $sock $node
|
|
pluginCapsInitialize $node "mobmodel"
|
|
}
|
|
foreach link $link_list { sendLinkMessage $sock $link add }
|
|
# GUI-specific meta-data send via Configure Messages
|
|
sendMetaData $sock $annotation_list "annotation"
|
|
sendMetaData $sock $canvas_list "canvas"
|
|
set obj "metadata"
|
|
set values [getGlobalOptionList]
|
|
sendConfReplyMessage $sock -1 $obj "10" "{global_options=$values}" ""
|
|
if { [info exists g_comments] && $g_comments != "" } {
|
|
sendConfReplyMessage $sock -1 $obj "10" "{comments=$g_comments}" ""
|
|
}
|
|
}
|
|
|
|
proc getNodeTypeAPI { node } {
|
|
set type [nodeType $node]
|
|
if { $type == "router" } {
|
|
set model [getNodeModel $node]
|
|
set type [getNodeTypeMachineType $model]
|
|
}
|
|
switch -exact -- "$type" {
|
|
router { return 0x0 }
|
|
netns { return 0x0 }
|
|
jail { return 0x0 }
|
|
physical { return 0x1 }
|
|
xen { return 0x2 }
|
|
tbd { return 0x3 }
|
|
lanswitch { return 0x4 }
|
|
hub { return 0x5 }
|
|
wlan { return 0x6 }
|
|
rj45 { return 0x7 }
|
|
tunnel { return 0x8 }
|
|
ktunnel { return 0x9 }
|
|
emane { return 0xA }
|
|
default { return 0x0 }
|
|
}
|
|
}
|
|
|
|
# send an Execute message
|
|
proc sendExecMessage { channel node cmd exec_num flags } {
|
|
global showAPI g_api_exec_num
|
|
set prmsg $showAPI
|
|
|
|
set node_num [string range $node 1 end]
|
|
set cmd_len [string length $cmd]
|
|
if { $cmd_len > 255 } { puts "sendExecMessage error: cmd too long!"; return}
|
|
set cmd_pad_len [pad_32bit $cmd_len]
|
|
set cmd_pad [binary format x$cmd_pad_len]
|
|
|
|
if { $exec_num == 0 } {
|
|
incr g_api_exec_num
|
|
set exec_num $g_api_exec_num
|
|
}
|
|
|
|
# node num + exec num + command string
|
|
set len [expr {8 + 8 + 2 + $cmd_len + $cmd_pad_len}]
|
|
|
|
if { $prmsg == 1 } {puts ">EXEC(flags=$flags,$node,n=$exec_num,cmd='$cmd')" }
|
|
|
|
set msg [binary format ccSc2sIc2sIcc \
|
|
3 $flags $len \
|
|
{1 4} 0 $node_num \
|
|
{2 4} 0 $exec_num \
|
|
4 $cmd_len \
|
|
]
|
|
puts -nonewline $channel $msg$cmd$cmd_pad
|
|
flushChannel channel "Error sending file message"
|
|
}
|
|
|
|
# if source file (sf) is specified, then send a message that the file source
|
|
# file should be copied to the given file name (f); otherwise, include the file
|
|
# data in this message
|
|
proc sendFileMessage { channel node type f sf data data_len } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
|
|
set node_num [string range $node 1 end]
|
|
|
|
set f_len [string length $f]
|
|
set f_pad_len [pad_32bit $f_len]
|
|
set f_pad [binary format x$f_pad_len]
|
|
set type_len [string length $type]
|
|
set type_pad_len [pad_32bit $type_len]
|
|
set type_pad [binary format x$type_pad_len]
|
|
if { $sf != "" } {
|
|
set sf_len [string length $sf]
|
|
set sf_pad_len [pad_32bit $sf_len]
|
|
set sf_pad [binary format x$sf_pad_len]
|
|
set data_len 0
|
|
set data_pad_len 0
|
|
} else {
|
|
set sf_len 0
|
|
set sf_pad_len 0
|
|
set data_pad_len [pad_32bit $data_len]
|
|
set data_pad [binary format x$data_pad_len]
|
|
}
|
|
# TODO: gzip compression w/tlv type 0x11
|
|
|
|
# node number TLV + file name TLV + ( file src name / data TLV)
|
|
set len [expr {8 + 2 + 2 + $f_len + $f_pad_len + $sf_len + $sf_pad_len \
|
|
+ $data_len + $data_pad_len}]
|
|
# 16-bit data length
|
|
if { $data_len > 255 } {
|
|
incr len 2
|
|
if { $data_len > 65536 } {
|
|
puts -nonewline "*** error: File Message data length too large "
|
|
puts "($data_len > 65536)"
|
|
return
|
|
}
|
|
}
|
|
if { $type_len > 0 } { incr len [expr {2 + $type_len + $type_pad_len}] }
|
|
set flags 1; # add flag
|
|
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">FILE(flags=$flags,$node,f=$f,"
|
|
if { $type != "" } { puts -nonewline "type=$type," }
|
|
if { $sf != "" } { puts "src=$sf)"
|
|
} else { puts "data=($data_len))" }
|
|
}
|
|
|
|
set msg [binary format ccSc2sIcc \
|
|
6 $flags $len \
|
|
{1 4} 0 $node_num \
|
|
2 $f_len \
|
|
]
|
|
set msg2 ""
|
|
if { $type_len > 0 } {
|
|
set msg2 [binary format cc 0x5 $type_len]
|
|
set msg2 $msg2$type$type_pad
|
|
}
|
|
if { $sf != "" } { ;# source file name TLV
|
|
set msg3 [binary format cc 0x6 $sf_len]
|
|
puts -nonewline $channel $msg$f$f_pad$msg2$msg3$sf$sf_pad
|
|
} else { ;# file data TLV
|
|
if { $data_len > 255 } {
|
|
set msg3 [binary format ccS 0x10 0 $data_len]
|
|
} else {
|
|
set msg3 [binary format cc 0x10 $data_len]
|
|
}
|
|
puts -nonewline $channel $msg$f$f_pad$msg2$msg3$data$data_pad
|
|
}
|
|
flushChannel channel "Error sending file message"
|
|
}
|
|
|
|
# Session Message
|
|
proc sendSessionMessage { channel flags num name sfile nodecount tf user } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
|
|
if { $channel == -1 } {
|
|
set pname [lindex [getEmulPlugin "*"] 0]
|
|
set channel [pluginConnect $pname connect true]
|
|
if { $channel == -1 } { return }
|
|
}
|
|
|
|
set num_len [string length $num]
|
|
set num_pad_len [pad_32bit $num_len]
|
|
set len [expr {2 + $num_len + $num_pad_len}]
|
|
if { $num_len <= 0 } {
|
|
puts "error: sendSessionMessage requires at least one session number"
|
|
return
|
|
}
|
|
set name_len [string length $name]
|
|
set name_pad_len [pad_32bit $name_len]
|
|
if { $name_len > 0 } { incr len [expr { 2 + $name_len + $name_pad_len }] }
|
|
set sfile_len [string length $sfile]
|
|
set sfile_pad_len [pad_32bit $sfile_len]
|
|
if { $sfile_len > 0 } {
|
|
incr len [expr { 2 + $sfile_len + $sfile_pad_len }]
|
|
}
|
|
set nc_len [string length $nodecount]
|
|
set nc_pad_len [pad_32bit $nc_len]
|
|
if { $nc_len > 0 } { incr len [expr { 2 + $nc_len + $nc_pad_len }] }
|
|
set tf_len [string length $tf]
|
|
set tf_pad_len [pad_32bit $tf_len]
|
|
if { $tf_len > 0 } { incr len [expr { 2 + $tf_len + $tf_pad_len }] }
|
|
set user_len [string length $user]
|
|
set user_pad_len [pad_32bit $user_len]
|
|
if { $user_len > 0 } { incr len [expr { 2 + $user_len + $user_pad_len }] }
|
|
|
|
if { $prmsg == 1 } {
|
|
puts -nonewline ">SESSION(flags=$flags" }
|
|
set msgh [binary format ccS 0x09 $flags $len ] ;# message header
|
|
|
|
if { $prmsg == 1 } { puts -nonewline ",sids=$num" }
|
|
set num_hdr [binary format cc 0x01 $num_len]
|
|
set num_pad [binary format x$num_pad_len ]
|
|
set msg1 "$num_hdr$num$num_pad"
|
|
|
|
set msg2 ""
|
|
if { $name_len > 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline ",name=$name" }
|
|
# TODO: name_len > 255
|
|
set name_hdr [binary format cc 0x02 $name_len]
|
|
set name_pad [binary format x$name_pad_len]
|
|
set msg2 "$name_hdr$name$name_pad"
|
|
}
|
|
set msg3 ""
|
|
if { $sfile_len > 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline ",file=$sfile" }
|
|
# TODO: sfile_len > 255
|
|
set sfile_hdr [binary format cc 0x03 $sfile_len]
|
|
set sfile_pad [binary format x$sfile_pad_len]
|
|
set msg3 "$sfile_hdr$sfile$sfile_pad"
|
|
}
|
|
set msg4 ""
|
|
if { $nc_len > 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline ",nc=$nodecount" }
|
|
set nc_hdr [binary format cc 0x04 $nc_len]
|
|
set nc_pad [binary format x$nc_pad_len]
|
|
set msg4 "$nc_hdr$nodecount$nc_pad"
|
|
}
|
|
set msg5 ""
|
|
if { $tf_len > 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline ",thumb=$tf" }
|
|
set tf_hdr [binary format cc 0x06 $tf_len]
|
|
set tf_pad [binary format x$tf_pad_len]
|
|
set msg5 "$tf_hdr$tf$tf_pad"
|
|
}
|
|
set msg6 ""
|
|
if { $user_len > 0 } {
|
|
if { $prmsg == 1 } { puts -nonewline ",user=$user" }
|
|
set user_hdr [binary format cc 0x07 $user_len]
|
|
set user_pad [binary format x$user_pad_len]
|
|
set msg6 "$user_hdr$user$user_pad"
|
|
}
|
|
|
|
if { $prmsg == 1 } { puts ")" }
|
|
puts -nonewline $channel $msgh$msg1$msg2$msg3$msg4$msg5$msg6
|
|
flushChannel channel "Error sending Session num=$num"
|
|
}
|
|
|
|
# return a new execution number and record it in the execution request list
|
|
# for the given callback (e.g. widget) type
|
|
proc newExecCallbackRequest { type } {
|
|
global g_api_exec_num g_execRequests
|
|
incr g_api_exec_num
|
|
set exec_num $g_api_exec_num
|
|
lappend g_execRequests($type) $exec_num
|
|
return $exec_num
|
|
}
|
|
|
|
# ask daemon to load or save an XML file based on the current session
|
|
proc xmlFileLoadSave { cmd name } {
|
|
global oper_mode eventtypes
|
|
|
|
set plugin [lindex [getEmulPlugin "*"] 0]
|
|
set sock [pluginConnect $plugin connect true]
|
|
if { $sock == -1 || $sock == "" } { return }
|
|
|
|
# inform daemon about nodes and links when saving in edit mode
|
|
if { $cmd == "save" && $oper_mode != "exec" } {
|
|
sendSessionProperties $sock
|
|
# this tells the CORE services that we are starting to send
|
|
# configuration data
|
|
# clear any existing config
|
|
sendEventMessage $sock $eventtypes(definition_state) -1 "" "" 0
|
|
sendEventMessage $sock $eventtypes(configuration_state) -1 "" "" 0
|
|
sendEmulationServerInfo $sock 0
|
|
sendSessionOptions $sock
|
|
sendHooks $sock
|
|
sendCanvasInfo $sock
|
|
sendNodeTypeInfo $sock 0
|
|
# send any custom service info before the node messages
|
|
sendNodeCustomServices $sock
|
|
sendNodeLinkDefinitions $sock
|
|
} elseif { $cmd == "open" } {
|
|
# reset config objects
|
|
sendNodeTypeInfo $sock 1
|
|
}
|
|
sendEventMessage $sock $eventtypes(file_$cmd) -1 $name "" 0
|
|
}
|
|
|
|
############################################################################
|
|
#
|
|
# Helper functions below here
|
|
#
|
|
|
|
# helper function to get interface number from name
|
|
proc ifcNameToNum { ifc } {
|
|
# eth0, eth1, etc.
|
|
if {[string range $ifc 0 2] == "eth"} {
|
|
set ifnum [string range $ifc 3 end]
|
|
# l0, l1, etc.
|
|
} else {
|
|
set ifnum [string range $ifc 1 end]
|
|
}
|
|
if { $ifnum == "" } {
|
|
return -1
|
|
}
|
|
if {![string is integer $ifnum]} {
|
|
return -1
|
|
}
|
|
return $ifnum
|
|
}
|
|
|
|
#
|
|
# parse the type and length from a TLV header
|
|
proc parseTLVHeader { data current_ref } {
|
|
global showAPI
|
|
set prmsg $showAPI
|
|
upvar $current_ref current
|
|
|
|
if { [binary scan $data @${current}cc type length] != 2 } {
|
|
if { $prmsg == 1 } { puts "TLV header error" }
|
|
return ""
|
|
}
|
|
set length [expr {$length & 0xFF}]; # convert signed to unsigned
|
|
if { $length == 0 } {
|
|
if { $type == 0 } {
|
|
# prevent endless looping
|
|
if { $prmsg == 1 } { puts -nonewline "(extra padding)" }
|
|
return ""
|
|
} else {
|
|
# support for length > 255
|
|
incr current 2
|
|
if { [binary scan $data @${current}S length] != 1 } {
|
|
puts "error reading TLV length (type=$type)"
|
|
return ""
|
|
}
|
|
set length [expr {$length & 0xFFFF}]
|
|
if { $length == 0 } {
|
|
# zero-length string, not length > 255
|
|
incr current -2
|
|
}
|
|
}
|
|
}
|
|
incr current 2
|
|
return [list $type $length]
|
|
}
|
|
|
|
# return the binary string, and length by reference
|
|
proc buildStringTLV { type data len_ref } {
|
|
upvar $len_ref len
|
|
set data_len [string length $data]
|
|
if { $data_len > 65536 } {
|
|
puts "warning: buildStringTLV data truncated"
|
|
set data_len 65536
|
|
set data [string range 0 65535]
|
|
}
|
|
set data_pad_len [pad_32bit $data_len]
|
|
set data_pad [binary format x$data_pad_len]
|
|
|
|
if { $data_len == 0 } {
|
|
set len 0
|
|
return ""
|
|
}
|
|
|
|
if { $data_len > 255 } {
|
|
set hdr [binary format ccS $type 0 $data_len]
|
|
set hdr_len 4
|
|
} else {
|
|
set hdr [binary format cc $type $data_len]
|
|
set hdr_len 2
|
|
}
|
|
|
|
set len [expr {$hdr_len + $data_len + $data_pad_len}]
|
|
|
|
return $hdr$data$data_pad
|
|
}
|
|
|
|
# calculate padding to 32-bit word boundary
|
|
# 32-bit and 64-bit values are pre-padded, strings and 128-bit values are
|
|
# post-padded to word boundary, depending on type
|
|
proc pad_32bit { len } {
|
|
# total length = 2 + len + pad
|
|
if { $len < 256 } {
|
|
set hdrsiz 2
|
|
} else {
|
|
set hdrsiz 4
|
|
}
|
|
# calculate padding to fill 32-bit boundary
|
|
return [expr { -($hdrsiz + $len) % 4 }]
|
|
}
|
|
|
|
proc macToString { mac_num } {
|
|
set mac_bytes ""
|
|
# convert 64-bit integer into 12-digit hex string
|
|
set mac_num 0x[format "%.12lx" $mac_num]
|
|
while { $mac_num > 0 } {
|
|
# append 8-bit hex number to list
|
|
set uchar [format "%02x" [expr $mac_num & 0xFF]]
|
|
lappend mac_bytes $uchar
|
|
# shift off 8-bits
|
|
set mac_num [expr $mac_num >> 8]
|
|
}
|
|
|
|
# make sure we have six hex digits
|
|
set num_zeroes [expr 6 - [llength $mac_bytes]]
|
|
while { $num_zeroes > 0 } {
|
|
lappend mac_bytes 00
|
|
incr num_zeroes -1
|
|
}
|
|
|
|
# this is lreverse in tcl8.5 and later
|
|
set r {}
|
|
set i [llength $mac_bytes]
|
|
while { $i > 0 } { lappend r [lindex $mac_bytes [incr i -1]] }
|
|
|
|
return [join $r :]
|
|
}
|
|
|
|
proc hexdump { data } {
|
|
# read data as hex
|
|
binary scan $data H* hex
|
|
# split into pairs of hex digits
|
|
regsub -all -- {..} $hex {& } hex
|
|
return $hex
|
|
}
|