PageRenderTime 115ms CodeModel.GetById 3ms app.highlight 100ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/common_test/src/ct_netconfc.erl

https://github.com/cobusc/otp
Erlang | 1818 lines | 1389 code | 170 blank | 259 comment | 6 complexity | 04ec3c3e2e5a7b084dd59196da8a63cc MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1%%----------------------------------------------------------------------
   2%% %CopyrightBegin%
   3%%
   4%% Copyright Ericsson AB 2012-2017. All Rights Reserved.
   5%%
   6%% Licensed under the Apache License, Version 2.0 (the "License");
   7%% you may not use this file except in compliance with the License.
   8%% You may obtain a copy of the License at
   9%%
  10%%     http://www.apache.org/licenses/LICENSE-2.0
  11%%
  12%% Unless required by applicable law or agreed to in writing, software
  13%% distributed under the License is distributed on an "AS IS" BASIS,
  14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15%% See the License for the specific language governing permissions and
  16%% limitations under the License.
  17%%
  18%% %CopyrightEnd%
  19%%
  20%%----------------------------------------------------------------------
  21%% File: ct_netconfc.erl
  22%%
  23%% Description:
  24%%    This file contains the Netconf client interface
  25%%
  26%% Netconf servers can be configured by adding the following statement
  27%% to a configuration file:
  28%%
  29%% {server_id(),options()}.
  30%%
  31%% The server_id() or an associated ct:target_name() shall then be
  32%% used in calls to open/2 connect/2.
  33%%
  34%% If no configuration exists for a server, use open/1 and connect/1.
  35%%
  36%% == Logging ==
  37%%
  38%% The netconf server uses the `error_logger' for logging of netconf
  39%% traffic. A special purpose error handler is implemented in
  40%% `ct_conn_log_h'. To use this error handler, add the `cth_conn_log'
  41%% hook in your test suite, e.g.
  42%%
  43%% suite() ->
  44%%     [{ct_hooks, [{cth_conn_log, [{ct:conn_log_mod(),ct:conn_log_options()}]}]}].
  45%%
  46%% For example:
  47%%
  48%% suite() ->
  49%%     [{ct_hooks,
  50%%         [{cth_conn_log,[{ct_netconfc,[{log_type,pretty},
  51%%                                       {hosts,[my_configured_server]}]}]}
  52%%
  53%% == Notifications ==
  54%%
  55%% The netconf client is also compliant with RFC5277 NETCONF Event
  56%% Notifications, which defines a mechanism for an asynchronous
  57%% message notification delivery service for the netconf protocol.
  58%%
  59%% Specific functions to support this are create_subscription/6
  60%% get_event_streams/3. (The functions also exist with other arities.)
  61%%
  62%%----------------------------------------------------------------------
  63-module(ct_netconfc).
  64
  65-include("ct_netconfc.hrl").
  66-include("ct_util.hrl").
  67-include_lib("xmerl/include/xmerl.hrl").
  68
  69%%----------------------------------------------------------------------
  70%% External exports
  71%%----------------------------------------------------------------------
  72-export([connect/1,
  73         connect/2,
  74         disconnect/1,
  75         session/1,
  76         session/2,
  77         session/3,
  78         open/1,
  79	 open/2,
  80	 only_open/1,
  81	 only_open/2,
  82	 hello/1,
  83	 hello/2,
  84	 hello/3,
  85	 close_session/1,
  86	 close_session/2,
  87	 kill_session/2,
  88	 kill_session/3,
  89	 send/2,
  90	 send/3,
  91	 send_rpc/2,
  92	 send_rpc/3,
  93	 lock/2,
  94	 lock/3,
  95	 unlock/2,
  96	 unlock/3,
  97	 get/2,
  98	 get/3,
  99	 get_config/3,
 100	 get_config/4,
 101	 edit_config/3,
 102	 edit_config/4,
 103	 edit_config/5,
 104	 delete_config/2,
 105	 delete_config/3,
 106	 copy_config/3,
 107	 copy_config/4,
 108	 action/2,
 109	 action/3,
 110	 create_subscription/1,
 111	 create_subscription/2,
 112	 create_subscription/3,
 113	 create_subscription/4,
 114	 create_subscription/5,
 115	 create_subscription/6,
 116	 get_event_streams/1,
 117	 get_event_streams/2,
 118	 get_event_streams/3,
 119	 get_capabilities/1,
 120	 get_capabilities/2,
 121	 get_session_id/1,
 122	 get_session_id/2]).
 123
 124%%----------------------------------------------------------------------
 125%% Exported types
 126%%----------------------------------------------------------------------
 127-export_type([client/0,
 128              handle/0,
 129              notification/0]).
 130
 131%%----------------------------------------------------------------------
 132%% Internal exports
 133%%----------------------------------------------------------------------
 134%% ct_gen_conn callbacks
 135-export([init/3,
 136	 handle_msg/3,
 137	 handle_msg/2,
 138	 terminate/2,
 139	 close/1]).
 140
 141%% ct_conn_log callback
 142-export([format_data/2]).
 143
 144%%----------------------------------------------------------------------
 145%% Internal defines
 146%%----------------------------------------------------------------------
 147-define(APPLICATION,?MODULE).
 148-define(DEFAULT_STREAM,"NETCONF").
 149
 150-define(error(ConnName,Report),
 151	error_logger:error_report([{ct_connection,ConnName},
 152				   {client,self()},
 153				   {module,?MODULE},
 154				   {line,?LINE} |
 155				   Report])).
 156
 157-define(is_timeout(T), (is_integer(T) orelse T==infinity)).
 158-define(is_filter(F),
 159	(?is_simple_xml(F)
 160	 orelse (F==[])
 161	 orelse (is_list(F) andalso ?is_simple_xml(hd(F))))).
 162-define(is_simple_xml(Xml),
 163	(is_atom(Xml) orelse (is_tuple(Xml) andalso is_atom(element(1,Xml))))).
 164-define(is_string(S), (is_list(S) andalso is_integer(hd(S)))).
 165
 166%%----------------------------------------------------------------------
 167%% Records
 168%%----------------------------------------------------------------------
 169%% Client state
 170-record(state, {host,
 171		port,
 172		connection,      % #connection
 173		capabilities,
 174		session_id,
 175		msg_id = 1,
 176		hello_status,
 177		no_end_tag_buff = <<>>,
 178		buff = <<>>,
 179		pending = [],    % [#pending]
 180		event_receiver}).% pid
 181
 182%% Run-time client options.
 183-record(options, {ssh = [], % Options for the ssh application
 184		  host,
 185		  port = ?DEFAULT_PORT,
 186		  timeout = ?DEFAULT_TIMEOUT,
 187		  name,
 188                  type}).
 189
 190%% Connection reference
 191-record(connection, {reference, % {CM,Ch}
 192		     host,
 193		     port,
 194		     name,
 195                     type}).
 196
 197%% Pending replies from server
 198-record(pending, {tref,    % timer ref (returned from timer:xxx)
 199		  ref,     % pending ref
 200		  msg_id,
 201		  op,
 202		  caller}).% pid which sent the request
 203
 204%%----------------------------------------------------------------------
 205%% Type declarations
 206%%----------------------------------------------------------------------
 207-type client() :: handle() | server_id() | ct:target_name().
 208-opaque handle() :: pid().
 209
 210-type options() :: [option()].
 211-type option() :: {ssh,host()} | {port,inet:port_number()} | {user,string()} |
 212		  {password,string()} | {user_dir,string()} |
 213		  {timeout,timeout()}.
 214
 215-type session_options() :: [session_option()].
 216-type session_option() :: {timeout,timeout()}.
 217
 218-type host() :: inet:hostname() | inet:ip_address().
 219
 220-type notification() :: {notification, xml_attributes(), notification_content()}.
 221-type notification_content() :: [event_time()|simple_xml()].
 222-type event_time() :: {eventTime,xml_attributes(),[xs_datetime()]}.
 223
 224-type stream_name() :: string().
 225-type streams() :: [{stream_name(),[stream_data()]}].
 226-type stream_data() :: {description,string()} |
 227		       {replaySupport,string()} |
 228		       {replayLogCreationTime,string()} |
 229		       {replayLogAgedTime,string()}.
 230%% See XML Schema for Event Notifications found in RFC5277 for further
 231%% detail about the data format for the string values.
 232
 233-type error_reason() :: term().
 234
 235-type server_id() :: atom().
 236
 237-type simple_xml() :: {xml_tag(), xml_attributes(), xml_content()} |
 238		      {xml_tag(), xml_content()} |
 239		      xml_tag().
 240-type xml_tag() :: atom().
 241-type xml_attributes() :: [{xml_attribute_tag(),xml_attribute_value()}].
 242-type xml_attribute_tag() :: atom().
 243-type xml_attribute_value() :: string().
 244-type xml_content() :: [simple_xml() | iolist()].
 245-type xpath() :: {xpath,string()}.
 246
 247-type netconf_db() :: running | startup | candidate.
 248-type xs_datetime() :: string().
 249%% This date and time identifyer has the same format as the XML type
 250%% dateTime and compliant to RFC3339. The format is
 251%% "[-]CCYY-MM-DDThh:mm:ss[.s][Z|(+|-)hh:mm]"
 252
 253%%----------------------------------------------------------------------
 254%% External interface functions
 255%%----------------------------------------------------------------------
 256
 257%%----------------------------------------------------------------------
 258%% Open an SSH connection to a Netconf server
 259%% If the server options are specified in a configuration file, use
 260%% open/2.
 261-spec connect(Options) -> Result when
 262      Options :: options(),
 263      Result :: {ok,handle()} | {error,error_reason()}.
 264connect(Options) ->
 265    do_connect(Options, #options{type=connection},[]).
 266
 267-spec connect(KeyOrName,ExtraOptions) -> Result when
 268      KeyOrName :: ct:key_or_name(),
 269      ExtraOptions :: options(),
 270      Result :: {ok,handle()} | {error,error_reason()}.
 271connect(KeyOrName, ExtraOptions) ->
 272    SortedExtra = lists:keysort(1,ExtraOptions),
 273    SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
 274    AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
 275    do_connect(AllOpts,#options{name=KeyOrName,type=connection},[{name,KeyOrName}]).
 276
 277do_connect(OptList,InitOptRec,NameOpt) ->
 278    case check_options(OptList,InitOptRec) of
 279	{Host,Port,Options} ->
 280	    ct_gen_conn:start({Host,Port},Options,?MODULE,
 281                              NameOpt ++ [{reconnect,false},
 282                                          {use_existing_connection,false},
 283                                          {forward_messages,false}]);
 284	Error ->
 285	    Error
 286    end.
 287
 288%%----------------------------------------------------------------------
 289%% Close the given SSH connection.
 290-spec disconnect(Conn) -> ok | {error,error_reason()} when
 291      Conn :: handle().
 292disconnect(Conn) ->
 293    case call(Conn,get_ssh_connection) of
 294        {ok,_} ->
 295            ct_gen_conn:stop(Conn);
 296        Error ->
 297            Error
 298    end.
 299
 300%%----------------------------------------------------------------------
 301%% Open a netconf session as a channel on the given SSH connection,
 302%% and exchange `hello' messages.
 303-spec session(Conn) -> Result when
 304      Conn :: handle(),
 305      Result :: {ok,handle()} | {error,error_reason()}.
 306session(Conn) ->
 307    do_session(Conn,[],#options{type=channel},[]).
 308
 309-spec session(Conn,Options) -> Result when
 310      Conn :: handle(),
 311      Options :: session_options(),
 312      Result :: {ok,handle()} | {error,error_reason()};
 313             (KeyOrName,Conn) -> Result when
 314      KeyOrName :: ct:key_or_name(),
 315      Conn :: handle(),
 316      Result :: {ok,handle()} | {error,error_reason()}.
 317session(Conn,Options) when is_list(Options) ->
 318    do_session(Conn,Options,#options{type=channel},[]);
 319session(KeyOrName,Conn) ->
 320    do_session(Conn,[],#options{name=KeyOrName,type=channel},[{name,KeyOrName}]).
 321
 322-spec session(KeyOrName,Conn,Options) -> Result when
 323      Conn :: handle(),
 324      Options :: session_options(),
 325      KeyOrName :: ct:key_or_name(),
 326      Result :: {ok,handle()} | {error,error_reason()}.
 327session(KeyOrName,Conn,ExtraOptions) ->
 328    SortedExtra = lists:keysort(1,ExtraOptions),
 329    SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
 330    AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
 331    do_session(Conn,AllOpts,#options{name=KeyOrName,type=channel},
 332               [{name,KeyOrName}]).
 333
 334do_session(Conn,OptList,InitOptRec,NameOpt) ->
 335    case call(Conn,get_ssh_connection) of
 336        {ok,SshConn} ->
 337            case check_session_options(OptList,InitOptRec) of
 338                {ok,Options} ->
 339                    case ct_gen_conn:start(SshConn,Options,?MODULE,
 340                                           NameOpt ++
 341                                               [{reconnect,false},
 342                                                {use_existing_connection,false},
 343                                                {forward_messages,true}]) of
 344                        {ok,Client} ->
 345                            case hello(Client,Options#options.timeout) of
 346                                ok ->
 347                                    {ok,Client};
 348                                Error ->
 349                                    Error
 350                            end;
 351                        Error ->
 352                            Error
 353                    end;
 354                Error ->
 355                    Error
 356            end;
 357	Error ->
 358	    Error
 359    end.
 360
 361%%----------------------------------------------------------------------
 362%% Open a netconf session and exchange 'hello' messages.
 363%% If the server options are specified in a configuration file, use
 364%% open/2.
 365-spec open(Options) -> Result when
 366      Options :: options(),
 367      Result :: {ok,handle()} | {error,error_reason()}.
 368open(Options) ->
 369    open(Options,#options{type=connection_and_channel},[],true).
 370
 371-spec open(KeyOrName, ExtraOptions) -> Result when
 372      KeyOrName :: ct:key_or_name(),
 373      ExtraOptions :: options(),
 374      Result :: {ok,handle()} | {error,error_reason()}.
 375open(KeyOrName, ExtraOpts) ->
 376    open(KeyOrName, ExtraOpts, true).
 377
 378open(KeyOrName, ExtraOpts, Hello) ->
 379    SortedExtra = lists:keysort(1,ExtraOpts),
 380    SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
 381    AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
 382    open(AllOpts,#options{name=KeyOrName,type=connection_and_channel},
 383         [{name,KeyOrName}],Hello).
 384
 385open(OptList,InitOptRec,NameOpt,Hello) ->
 386    case check_options(OptList,InitOptRec) of
 387	{Host,Port,Options} ->
 388	    case ct_gen_conn:start({Host,Port},Options,?MODULE,
 389				   NameOpt ++ [{reconnect,false},
 390					       {use_existing_connection,false},
 391					       {forward_messages,true}]) of
 392		{ok,Client} when Hello==true ->
 393		    case hello(Client,Options#options.timeout) of
 394			ok ->
 395			    {ok,Client};
 396			Error ->
 397			    Error
 398		    end;
 399		Other ->
 400		    Other
 401	    end;
 402	Error ->
 403	    Error
 404    end.
 405
 406
 407%%----------------------------------------------------------------------
 408%% As open/1,2, except no 'hello' message is sent.
 409-spec only_open(Options) -> Result when
 410      Options :: options(),
 411      Result :: {ok,handle()} | {error,error_reason()}.
 412only_open(Options) ->
 413    open(Options,#options{type=connection_and_channel},[],false).
 414
 415-spec only_open(KeyOrName,ExtraOptions) -> Result when
 416      KeyOrName :: ct:key_or_name(),
 417      ExtraOptions :: options(),
 418      Result :: {ok,handle()} | {error,error_reason()}.
 419only_open(KeyOrName, ExtraOpts) ->
 420    open(KeyOrName, ExtraOpts, false).
 421
 422%%----------------------------------------------------------------------
 423%% Send a 'hello' message.
 424-spec hello(Client) -> Result when
 425      Client :: handle(),
 426      Result :: ok | {error,error_reason()}.
 427hello(Client) ->
 428    hello(Client,[],?DEFAULT_TIMEOUT).
 429
 430-spec hello(Client,Timeout) -> Result when
 431      Client :: handle(),
 432      Timeout :: timeout(),
 433      Result :: ok | {error,error_reason()}.
 434hello(Client,Timeout) ->
 435    hello(Client,[],Timeout).
 436
 437-spec hello(Client,Options,Timeout) -> Result when
 438      Client :: handle(),
 439      Options :: [{capability, [string()]}],
 440      Timeout :: timeout(),
 441      Result :: ok | {error,error_reason()}.
 442hello(Client,Options,Timeout) ->
 443    call(Client, {hello, Options, Timeout}).
 444
 445
 446%%----------------------------------------------------------------------
 447%% Get the session id for the session specified by Client.
 448-spec get_session_id(Client) -> Result when
 449      Client :: client(),
 450      Result :: pos_integer() | {error,error_reason()}.
 451get_session_id(Client) ->
 452    get_session_id(Client, ?DEFAULT_TIMEOUT).
 453
 454-spec get_session_id(Client, Timeout) -> Result when
 455      Client :: client(),
 456      Timeout :: timeout(),
 457      Result :: pos_integer() | {error,error_reason()}.
 458get_session_id(Client, Timeout) ->
 459    call(Client, get_session_id, Timeout).
 460
 461%%----------------------------------------------------------------------
 462%% Get the server side capabilities.
 463-spec get_capabilities(Client) -> Result when
 464      Client :: client(),
 465      Result :: [string()] | {error,error_reason()}.
 466get_capabilities(Client) ->
 467    get_capabilities(Client, ?DEFAULT_TIMEOUT).
 468
 469-spec get_capabilities(Client, Timeout) -> Result when
 470      Client :: client(),
 471      Timeout :: timeout(),
 472      Result :: [string()] | {error,error_reason()}.
 473get_capabilities(Client, Timeout) ->
 474    call(Client, get_capabilities, Timeout).
 475
 476%%----------------------------------------------------------------------
 477%% Send an XML document to the server.
 478-spec send(Client, SimpleXml) -> Result when
 479      Client :: client(),
 480      SimpleXml :: simple_xml(),
 481      Result :: simple_xml() | {error,error_reason()}.
 482send(Client, SimpleXml) ->
 483    send(Client, SimpleXml, ?DEFAULT_TIMEOUT).
 484
 485-spec send(Client, SimpleXml, Timeout) -> Result when
 486      Client :: client(),
 487      SimpleXml :: simple_xml(),
 488      Timeout :: timeout(),
 489      Result :: simple_xml() | {error,error_reason()}.
 490send(Client, SimpleXml, Timeout) ->
 491    call(Client,{send, Timeout, SimpleXml}).
 492
 493%%----------------------------------------------------------------------
 494%% Wrap the given XML document in a valid netconf 'rpc' request and
 495%% send to the server.
 496-spec send_rpc(Client, SimpleXml) -> Result when
 497      Client :: client(),
 498      SimpleXml :: simple_xml(),
 499      Result :: [simple_xml()] | {error,error_reason()}.
 500send_rpc(Client, SimpleXml) ->
 501    send_rpc(Client, SimpleXml, ?DEFAULT_TIMEOUT).
 502
 503-spec send_rpc(Client, SimpleXml, Timeout) -> Result when
 504      Client :: client(),
 505      SimpleXml :: simple_xml(),
 506      Timeout :: timeout(),
 507      Result :: [simple_xml()] | {error,error_reason()}.
 508send_rpc(Client, SimpleXml, Timeout) ->
 509    call(Client,{send_rpc, SimpleXml, Timeout}).
 510
 511%%----------------------------------------------------------------------
 512%% Send a 'lock' request.
 513-spec lock(Client, Target) -> Result when
 514      Client :: client(),
 515      Target :: netconf_db(),
 516      Result :: ok | {error,error_reason()}.
 517lock(Client, Target) ->
 518    lock(Client, Target,?DEFAULT_TIMEOUT).
 519
 520-spec lock(Client, Target, Timeout) -> Result when
 521      Client :: client(),
 522      Target :: netconf_db(),
 523      Timeout :: timeout(),
 524      Result :: ok | {error,error_reason()}.
 525lock(Client, Target, Timeout) ->
 526    call(Client,{send_rpc_op,lock,[Target],Timeout}).
 527
 528%%----------------------------------------------------------------------
 529%% Send a 'unlock' request.
 530-spec unlock(Client, Target) -> Result when
 531      Client :: client(),
 532      Target :: netconf_db(),
 533      Result :: ok | {error,error_reason()}.
 534unlock(Client, Target) ->
 535    unlock(Client, Target,?DEFAULT_TIMEOUT).
 536
 537-spec unlock(Client, Target, Timeout) -> Result when
 538      Client :: client(),
 539      Target :: netconf_db(),
 540      Timeout :: timeout(),
 541      Result :: ok | {error,error_reason()}.
 542unlock(Client, Target, Timeout) ->
 543    call(Client, {send_rpc_op, unlock, [Target], Timeout}).
 544
 545%%----------------------------------------------------------------------
 546%% Send a 'get' request.
 547-spec get(Client, Filter) -> Result when
 548      Client :: client(),
 549      Filter :: simple_xml() | xpath(),
 550      Result :: {ok,[simple_xml()]} | {error,error_reason()}.
 551get(Client, Filter) ->
 552    get(Client, Filter, ?DEFAULT_TIMEOUT).
 553
 554-spec get(Client, Filter, Timeout) -> Result when
 555      Client :: client(),
 556      Filter :: simple_xml() | xpath(),
 557      Timeout :: timeout(),
 558      Result :: {ok,[simple_xml()]} | {error,error_reason()}.
 559get(Client, Filter, Timeout) ->
 560    call(Client,{send_rpc_op, get, [Filter], Timeout}).
 561
 562%%----------------------------------------------------------------------
 563%% Send a 'get-config' request.
 564-spec get_config(Client, Source, Filter) -> Result when
 565      Client :: client(),
 566      Source :: netconf_db(),
 567      Filter :: simple_xml() | xpath(),
 568      Result :: {ok,[simple_xml()]} | {error,error_reason()}.
 569get_config(Client, Source, Filter) ->
 570    get_config(Client, Source, Filter, ?DEFAULT_TIMEOUT).
 571
 572-spec get_config(Client, Source, Filter, Timeout) -> Result when
 573      Client :: client(),
 574      Source :: netconf_db(),
 575      Filter :: simple_xml() | xpath(),
 576      Timeout :: timeout(),
 577      Result :: {ok,[simple_xml()]} | {error,error_reason()}.
 578get_config(Client, Source, Filter, Timeout) ->
 579    call(Client, {send_rpc_op, get_config, [Source, Filter], Timeout}).
 580
 581%%----------------------------------------------------------------------
 582%% Send a 'edit-config' request.
 583-spec edit_config(Client, Target, Config) -> Result when
 584      Client :: client(),
 585      Target :: netconf_db(),
 586      Config :: simple_xml(),
 587      Result :: ok | {error,error_reason()}.
 588edit_config(Client, Target, Config) ->
 589    edit_config(Client, Target, Config, ?DEFAULT_TIMEOUT).
 590
 591-spec edit_config(Client, Target, Config, OptParams) -> Result when
 592      Client :: client(),
 593      Target :: netconf_db(),
 594      Config :: simple_xml(),
 595      OptParams :: [simple_xml()],
 596      Result :: ok | {error,error_reason()};
 597                 (Client, Target, Config, Timeout) -> Result when
 598      Client :: client(),
 599      Target :: netconf_db(),
 600      Config :: simple_xml(),
 601      Timeout :: timeout(),
 602      Result :: ok | {error,error_reason()}.
 603edit_config(Client, Target, Config, Timeout) when ?is_timeout(Timeout) ->
 604    edit_config(Client, Target, Config, [], Timeout);
 605edit_config(Client, Target, Config, OptParams) when is_list(OptParams) ->
 606    edit_config(Client, Target, Config, OptParams, ?DEFAULT_TIMEOUT).
 607
 608-spec edit_config(Client, Target, Config, OptParams, Timeout) -> Result when
 609      Client :: client(),
 610      Target :: netconf_db(),
 611      Config :: simple_xml(),
 612      OptParams :: [simple_xml()],
 613      Timeout :: timeout(),
 614      Result :: ok | {error,error_reason()}.
 615edit_config(Client, Target, Config, OptParams, Timeout) ->
 616    call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
 617
 618
 619%%----------------------------------------------------------------------
 620%% Send a 'delete-config' request.
 621-spec delete_config(Client, Target) -> Result when
 622      Client :: client(),
 623      Target :: startup | candidate,
 624      Result :: ok | {error,error_reason()}.
 625delete_config(Client, Target) ->
 626    delete_config(Client, Target, ?DEFAULT_TIMEOUT).
 627
 628-spec delete_config(Client, Target, Timeout) -> Result when
 629      Client :: client(),
 630      Target :: startup | candidate,
 631      Timeout :: timeout(),
 632      Result :: ok | {error,error_reason()}.
 633delete_config(Client, Target, Timeout) when Target == startup;
 634					    Target == candidate ->
 635    call(Client,{send_rpc_op, delete_config, [Target], Timeout}).
 636
 637%%----------------------------------------------------------------------
 638%% Send a 'copy-config' request.
 639-spec copy_config(Client, Target, Source) -> Result when
 640      Client :: client(),
 641      Target :: netconf_db(),
 642      Source :: netconf_db(),
 643      Result :: ok | {error,error_reason()}.
 644copy_config(Client, Source, Target) ->
 645    copy_config(Client, Source, Target, ?DEFAULT_TIMEOUT).
 646
 647-spec copy_config(Client, Target, Source, Timeout) -> Result when
 648      Client :: client(),
 649      Target :: netconf_db(),
 650      Source :: netconf_db(),
 651      Timeout :: timeout(),
 652      Result :: ok | {error,error_reason()}.
 653copy_config(Client, Target, Source, Timeout) ->
 654    call(Client,{send_rpc_op, copy_config, [Target, Source], Timeout}).
 655
 656%%----------------------------------------------------------------------
 657%% Execute an action.
 658-spec action(Client, Action) -> Result when
 659      Client :: client(),
 660      Action :: simple_xml(),
 661      Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
 662action(Client,Action) ->
 663    action(Client,Action,?DEFAULT_TIMEOUT).
 664
 665-spec action(Client, Action, Timeout) -> Result when
 666      Client :: client(),
 667      Action :: simple_xml(),
 668      Timeout :: timeout(),
 669      Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
 670action(Client,Action,Timeout) ->
 671    call(Client,{send_rpc_op, action, [Action], Timeout}).
 672
 673%%----------------------------------------------------------------------
 674%% Send a 'create-subscription' request
 675%% See RFC5277, NETCONF Event Notifications
 676-spec create_subscription(Client) -> Result when
 677      Client :: client(),
 678      Result :: ok | {error,error_reason()}.
 679create_subscription(Client) ->
 680    create_subscription(Client,?DEFAULT_STREAM,?DEFAULT_TIMEOUT).
 681
 682-spec create_subscription(Client, Stream | Filter | Timeout) -> Result when
 683      Client :: client(),
 684      Stream :: stream_name(),
 685      Filter :: simple_xml() | [simple_xml()],
 686      Timeout :: timeout(),
 687      Result :: ok | {error,error_reason()}.
 688create_subscription(Client,Timeout)
 689  when ?is_timeout(Timeout) ->
 690    create_subscription(Client,?DEFAULT_STREAM,Timeout);
 691create_subscription(Client,Stream)
 692  when ?is_string(Stream) ->
 693    create_subscription(Client,Stream,?DEFAULT_TIMEOUT);
 694create_subscription(Client,Filter)
 695  when ?is_filter(Filter) ->
 696    create_subscription(Client,?DEFAULT_STREAM,Filter,
 697			?DEFAULT_TIMEOUT).
 698
 699create_subscription(Client,Stream,Timeout)
 700  when ?is_string(Stream) andalso
 701       ?is_timeout(Timeout) ->
 702    call(Client,{send_rpc_op,{create_subscription,self()},
 703		 [Stream,undefined,undefined,undefined],
 704		 Timeout});
 705create_subscription(Client,StartTime,StopTime)
 706  when ?is_string(StartTime) andalso
 707       ?is_string(StopTime) ->
 708    create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,
 709			?DEFAULT_TIMEOUT);
 710create_subscription(Client,Filter,Timeout)
 711  when ?is_filter(Filter) andalso
 712       ?is_timeout(Timeout) ->
 713    create_subscription(Client,?DEFAULT_STREAM,Filter,Timeout);
 714create_subscription(Client,Stream,Filter)
 715  when ?is_string(Stream) andalso
 716       ?is_filter(Filter) ->
 717    create_subscription(Client,Stream,Filter,?DEFAULT_TIMEOUT).
 718
 719create_subscription(Client,StartTime,StopTime,Timeout)
 720  when ?is_string(StartTime) andalso
 721       ?is_string(StopTime) andalso
 722       ?is_timeout(Timeout) ->
 723    create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,Timeout);
 724create_subscription(Client,Stream,StartTime,StopTime)
 725  when ?is_string(Stream) andalso
 726       ?is_string(StartTime) andalso
 727       ?is_string(StopTime) ->
 728    create_subscription(Client,Stream,StartTime,StopTime,?DEFAULT_TIMEOUT);
 729create_subscription(Client,Filter,StartTime,StopTime)
 730  when ?is_filter(Filter) andalso
 731       ?is_string(StartTime) andalso
 732       ?is_string(StopTime) ->
 733    create_subscription(Client,?DEFAULT_STREAM,Filter,
 734			StartTime,StopTime,?DEFAULT_TIMEOUT);
 735create_subscription(Client,Stream,Filter,Timeout)
 736  when ?is_string(Stream) andalso
 737       ?is_filter(Filter) andalso
 738       ?is_timeout(Timeout) ->
 739    call(Client,{send_rpc_op,{create_subscription,self()},
 740		 [Stream,Filter,undefined,undefined],
 741		 Timeout}).
 742
 743-spec create_subscription(Client, Stream, StartTime, StopTime, Timeout) ->
 744				 Result when
 745      Client :: client(),
 746      Stream :: stream_name(),
 747      StartTime :: xs_datetime(),
 748      StopTime :: xs_datetime(),
 749      Timeout :: timeout(),
 750      Result :: ok | {error,error_reason()};
 751                         (Client, Stream, Filter,StartTime, StopTime) ->
 752				 Result when
 753      Client :: client(),
 754      Stream :: stream_name(),
 755      Filter :: simple_xml() | [simple_xml()],
 756      StartTime :: xs_datetime(),
 757      StopTime :: xs_datetime(),
 758      Result :: ok | {error,error_reason()}.
 759create_subscription(Client,Stream,StartTime,StopTime,Timeout)
 760  when ?is_string(Stream) andalso
 761       ?is_string(StartTime) andalso
 762       ?is_string(StopTime) andalso
 763       ?is_timeout(Timeout) ->
 764    call(Client,{send_rpc_op,{create_subscription,self()},
 765		 [Stream,undefined,StartTime,StopTime],
 766		 Timeout});
 767create_subscription(Client,Stream,Filter,StartTime,StopTime)
 768  when ?is_string(Stream) andalso
 769       ?is_filter(Filter) andalso
 770       ?is_string(StartTime) andalso
 771       ?is_string(StopTime) ->
 772    create_subscription(Client,Stream,Filter,StartTime,StopTime,?DEFAULT_TIMEOUT).
 773
 774-spec create_subscription(Client, Stream, Filter,StartTime, StopTime, Timeout) ->
 775				 Result when
 776      Client :: client(),
 777      Stream :: stream_name(),
 778      Filter :: simple_xml() | [simple_xml()],
 779      StartTime :: xs_datetime(),
 780      StopTime :: xs_datetime(),
 781      Timeout :: timeout(),
 782      Result :: ok | {error,error_reason()}.
 783create_subscription(Client,Stream,Filter,StartTime,StopTime,Timeout) ->
 784    call(Client,{send_rpc_op,{create_subscription, self()},
 785		 [Stream,Filter,StartTime,StopTime],
 786		 Timeout}).
 787
 788%%----------------------------------------------------------------------
 789%% Send a request to get the given event streams
 790%% See RFC5277, NETCONF Event Notifications
 791-spec get_event_streams(Client)
 792		       -> Result when
 793      Client :: client(),
 794      Result :: {ok,streams()} | {error,error_reason()}.
 795get_event_streams(Client) ->
 796    get_event_streams(Client,[],?DEFAULT_TIMEOUT).
 797
 798-spec get_event_streams(Client, Timeout)
 799		       -> Result when
 800      Client :: client(),
 801      Timeout :: timeout(),
 802      Result :: {ok,streams()} | {error,error_reason()};
 803                       (Client, Streams) -> Result when
 804      Client :: client(),
 805      Streams :: [stream_name()],
 806      Result :: {ok,streams()} | {error,error_reason()}.
 807get_event_streams(Client,Timeout) when is_integer(Timeout); Timeout==infinity ->
 808    get_event_streams(Client,[],Timeout);
 809get_event_streams(Client,Streams) when is_list(Streams) ->
 810    get_event_streams(Client,Streams,?DEFAULT_TIMEOUT).
 811
 812-spec get_event_streams(Client, Streams, Timeout)
 813		       -> Result when
 814      Client :: client(),
 815      Streams :: [stream_name()],
 816      Timeout :: timeout(),
 817      Result :: {ok,streams()} | {error,error_reason()}.
 818get_event_streams(Client,Streams,Timeout) ->
 819    call(Client,{get_event_streams,Streams,Timeout}).
 820
 821
 822%%----------------------------------------------------------------------
 823%% Send a 'close-session' request
 824-spec close_session(Client) -> Result when
 825      Client :: client(),
 826      Result :: ok | {error,error_reason()}.
 827close_session(Client) ->
 828    close_session(Client, ?DEFAULT_TIMEOUT).
 829
 830-spec close_session(Client, Timeout) -> Result when
 831      Client :: client(),
 832      Timeout :: timeout(),
 833      Result :: ok | {error,error_reason()}.
 834close_session(Client, Timeout) ->
 835    call(Client,{send_rpc_op, close_session, [], Timeout}, true).
 836
 837
 838%%----------------------------------------------------------------------
 839%% Send a 'kill-session' request
 840-spec kill_session(Client, SessionId) -> Result when
 841      Client :: client(),
 842      SessionId :: pos_integer(),
 843      Result :: ok | {error,error_reason()}.
 844kill_session(Client, SessionId) ->
 845    kill_session(Client, SessionId, ?DEFAULT_TIMEOUT).
 846
 847-spec kill_session(Client, SessionId, Timeout) -> Result when
 848      Client :: client(),
 849      SessionId :: pos_integer(),
 850      Timeout :: timeout(),
 851      Result :: ok | {error,error_reason()}.
 852kill_session(Client, SessionId, Timeout) ->
 853    call(Client,{send_rpc_op, kill_session, [SessionId], Timeout}).
 854
 855
 856%%----------------------------------------------------------------------
 857%% Callback functions
 858%%----------------------------------------------------------------------
 859
 860init(_KeyOrName,{CM,{Host,Port}},Options) ->
 861    case ssh_channel(#connection{reference=CM,host=Host,port=Port},Options) of
 862        {ok,Connection} ->
 863	    {ok, CM, #state{connection = Connection}};
 864	{error,Reason}->
 865	    {error,Reason}
 866    end;
 867init(_KeyOrName,{_Host,_Port},Options) when Options#options.type==connection ->
 868    case ssh_connect(Options) of
 869        {ok, Connection} ->
 870	    ConnPid = Connection#connection.reference,
 871            {ok, ConnPid, #state{connection = Connection}};
 872        Error ->
 873            Error
 874    end;
 875init(_KeyOrName,{_Host,_Port},Options) ->
 876    case ssh_open(Options) of
 877	{ok, Connection} ->
 878	    {ConnPid,_} = Connection#connection.reference,
 879	    {ok, ConnPid, #state{connection = Connection}};
 880	{error,Reason}->
 881	    {error,Reason}
 882    end.
 883
 884
 885terminate(_, #state{connection=Connection}) ->
 886    ssh_close(Connection),
 887    ok.
 888
 889handle_msg({hello, Options, Timeout}, From,
 890	   #state{connection=Connection,hello_status=HelloStatus} = State) ->
 891    case do_send(Connection, client_hello(Options)) of
 892	ok ->
 893	    case HelloStatus of
 894		undefined ->
 895		    {Ref,TRef} = set_request_timer(Timeout),
 896		    {noreply, State#state{hello_status=#pending{tref=TRef,
 897								ref=Ref,
 898								caller=From}}};
 899		received ->
 900		    {reply, ok, State#state{hello_status=done}};
 901		{error,Reason} ->
 902		    {stop, {error,Reason}, State}
 903	    end;
 904	Error ->
 905	    {stop, Error, State}
 906    end;
 907handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
 908    Reply =
 909        case Connection#connection.reference of
 910            {_,_} -> {error,not_an_ssh_connection};
 911            CM -> {ok,{CM,{Connection#connection.host,
 912                           Connection#connection.port}}}
 913        end,
 914    {reply, Reply, State};
 915handle_msg(_, _From, #state{session_id=undefined} = State) ->
 916    %% Hello is not yet excanged - this shall never happen
 917    {reply,{error,waiting_for_hello},State};
 918handle_msg(get_capabilities, _From, #state{capabilities = Caps} = State) ->
 919    {reply, Caps, State};
 920handle_msg(get_session_id, _From, #state{session_id = Id} = State) ->
 921    {reply, Id, State};
 922handle_msg({send, Timeout, SimpleXml}, From,
 923	    #state{connection=Connection,pending=Pending} = State) ->
 924    case do_send(Connection, SimpleXml) of
 925	ok ->
 926	    {Ref,TRef} = set_request_timer(Timeout),
 927	    {noreply, State#state{pending=[#pending{tref=TRef,
 928						    ref=Ref,
 929						    caller=From} | Pending]}};
 930	Error ->
 931	    {reply, Error, State}
 932    end;
 933handle_msg({send_rpc, SimpleXml, Timeout}, From, State) ->
 934    do_send_rpc(undefined, SimpleXml, Timeout, From, State);
 935handle_msg({send_rpc_op, Op, Data, Timeout}, From, State) ->
 936    SimpleXml = encode_rpc_operation(Op,Data),
 937    do_send_rpc(Op, SimpleXml, Timeout, From, State);
 938handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
 939    Filter = {netconf,?NETMOD_NOTIF_NAMESPACE_ATTR,
 940             [{streams,[{stream,[{name,[Name]}]} || Name <- Streams]}]},
 941    SimpleXml = encode_rpc_operation(get,[Filter]),
 942    do_send_rpc(Op, SimpleXml, Timeout, From, State).
 943
 944handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
 945    ssh_connection:adjust_window(CM,Ch,size(Data)),
 946    handle_data(Data, State);
 947handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
 948    %% _SshCloseMsg can probably be one of
 949    %% {eof,Ch}
 950    %% {exit_status,Ch,Status}
 951    %% {exit_signal,Ch,ExitSignal,ErrorMsg,LanguageString}
 952    %% {signal,Ch,Signal}
 953
 954    %% This might e.g. happen if the server terminates the connection,
 955    %% as in kill-session (or if ssh:close is called from somewhere
 956    %% unexpected).
 957
 958    %%! Log this??
 959    %%! Currently the log will say that the client closed the
 960    %%! connection - due to terminate/2
 961
 962    {stop, State};
 963handle_msg({Ref,timeout},
 964	   #state{hello_status=#pending{ref=Ref,caller=Caller}} = State) ->
 965    ct_gen_conn:return(Caller,{error,{hello_session_failed,timeout}}),
 966    {stop,State#state{hello_status={error,timeout}}};
 967handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
 968    {value,#pending{op=Op,caller=Caller},Pending1} =
 969	lists:keytake(Ref,#pending.ref,Pending),
 970    ct_gen_conn:return(Caller,{error,timeout}),
 971    R = case Op of
 972	    close_session -> stop;
 973	    _ -> noreply
 974	end,
 975    %% Halfhearted try to get in correct state, this matches
 976    %% the implementation before this patch
 977    {R,State#state{pending=Pending1, no_end_tag_buff= <<>>, buff= <<>>}}.
 978
 979%% Called by ct_util_server to close registered connections before terminate.
 980close(Client) ->
 981    case get_handle(Client) of
 982	{ok,Pid} ->
 983	    case ct_gen_conn:stop(Pid) of
 984		{error,{process_down,Pid,noproc}} ->
 985		    {error,already_closed};
 986		Result ->
 987		    Result
 988	    end;
 989	Error ->
 990	    Error
 991    end.
 992
 993
 994%%----------------------------------------------------------------------
 995%% Internal functions
 996%%----------------------------------------------------------------------
 997call(Client, Msg) ->
 998    call(Client, Msg, infinity, false).
 999call(Client, Msg, Timeout) when is_integer(Timeout); Timeout==infinity ->
1000    call(Client, Msg, Timeout, false);
1001call(Client, Msg, WaitStop) when is_boolean(WaitStop) ->
1002    call(Client, Msg, infinity, WaitStop).
1003call(Client, Msg, Timeout, WaitStop) ->
1004    case get_handle(Client) of
1005	{ok,Pid} ->
1006	    case ct_gen_conn:call(Pid,Msg,Timeout) of
1007		{error,{process_down,Pid,noproc}} ->
1008		    {error,no_such_client};
1009		{error,{process_down,Pid,normal}} when WaitStop ->
1010		    %% This will happen when server closes connection
1011		    %% before client received rpc-reply on
1012		    %% close-session.
1013		    ok;
1014		{error,{process_down,Pid,normal}} ->
1015		    {error,closed};
1016		{error,{process_down,Pid,Reason}} ->
1017		    {error,{closed,Reason}};
1018		Other when WaitStop ->
1019		    MRef = erlang:monitor(process,Pid),
1020		    receive
1021			{'DOWN',MRef,process,Pid,Normal} when Normal==normal;
1022							      Normal==noproc ->
1023			    Other;
1024			{'DOWN',MRef,process,Pid,Reason} ->
1025			    {error,{{closed,Reason},Other}}
1026		    after Timeout ->
1027			    erlang:demonitor(MRef, [flush]),
1028			    {error,{timeout,Other}}
1029		    end;
1030		Other ->
1031		    Other
1032	    end;
1033	Error ->
1034	    Error
1035    end.
1036
1037get_handle(Client) when is_pid(Client) ->
1038    {ok,Client};
1039get_handle(Client) ->
1040    case ct_util:get_connection(Client, ?MODULE) of
1041	{ok,{Pid,_}} ->
1042	    {ok,Pid};
1043	{error,no_registered_connection} ->
1044	    {error,{no_connection_found,Client}};
1045	Error ->
1046	    Error
1047    end.
1048
1049check_options(OptList,Options) ->
1050    check_options(OptList,undefined,undefined,Options).
1051
1052check_options([], undefined, _Port, _Options) ->
1053    {error, no_host_address};
1054check_options([], _Host, undefined, _Options) ->
1055    {error, no_port};
1056check_options([], Host, Port, Options) ->
1057    {Host,Port,Options};
1058check_options([{ssh, Host}|T], _, Port, Options) ->
1059    check_options(T, Host, Port, Options#options{host=Host});
1060check_options([{port,Port}|T], Host, _, Options) ->
1061    check_options(T, Host, Port, Options#options{port=Port});
1062check_options([{timeout, Timeout}|T], Host, Port, Options)
1063  when is_integer(Timeout); Timeout==infinity ->
1064    check_options(T, Host, Port, Options#options{timeout = Timeout});
1065check_options([{timeout, _} = Opt|_T], _Host, _Port, _Options) ->
1066    {error, {invalid_option, Opt}};
1067check_options([Opt|T], Host, Port, #options{ssh=SshOpts}=Options) ->
1068    %% Option verified by ssh
1069    check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}).
1070
1071check_session_options([],Options) ->
1072    {ok,Options};
1073check_session_options([{timeout, Timeout}|T], Options)
1074  when is_integer(Timeout); Timeout==infinity ->
1075    check_session_options(T, Options#options{timeout = Timeout});
1076check_session_options([Opt|_T], _Options) ->
1077    {error, {invalid_option, Opt}}.
1078
1079
1080%%%-----------------------------------------------------------------
1081set_request_timer(infinity) ->
1082    {undefined,undefined};
1083set_request_timer(T) ->
1084    Ref = make_ref(),
1085    {ok,TRef} = timer:send_after(T,{Ref,timeout}),
1086    {Ref,TRef}.
1087
1088%%%-----------------------------------------------------------------
1089cancel_request_timer(undefined,undefined) ->
1090    ok;
1091cancel_request_timer(Ref,TRef) ->
1092    _ = timer:cancel(TRef),
1093    receive {Ref,timeout} -> ok
1094    after 0 -> ok
1095    end.
1096
1097%%%-----------------------------------------------------------------
1098client_hello(Options) when is_list(Options) ->
1099    UserCaps = [{capability, UserCap} ||
1100		   {capability, UserCap} <- Options,
1101		   is_list(hd(UserCap))],
1102    {hello, ?NETCONF_NAMESPACE_ATTR,
1103     [{capabilities,
1104       [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}|
1105	UserCaps]}]}.
1106
1107%%%-----------------------------------------------------------------
1108
1109encode_rpc_operation(Lock,[Target]) when Lock==lock; Lock==unlock ->
1110    {Lock,[{target,[Target]}]};
1111encode_rpc_operation(get,[Filter]) ->
1112    {get,filter(Filter)};
1113encode_rpc_operation(get_config,[Source,Filter]) ->
1114    {'get-config',[{source,[Source]}] ++ filter(Filter)};
1115encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
1116    {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,[Config]}]};
1117encode_rpc_operation(delete_config,[Target]) ->
1118    {'delete-config',[{target,[Target]}]};
1119encode_rpc_operation(copy_config,[Target,Source]) ->
1120    {'copy-config',[{target,[Target]},{source,[Source]}]};
1121encode_rpc_operation(action,[Action]) ->
1122    {action,?ACTION_NAMESPACE_ATTR,[{data,[Action]}]};
1123encode_rpc_operation(kill_session,[SessionId]) ->
1124    {'kill-session',[{'session-id',[integer_to_list(SessionId)]}]};
1125encode_rpc_operation(close_session,[]) ->
1126    'close-session';
1127encode_rpc_operation({create_subscription,_},
1128		     [Stream,Filter,StartTime,StopTime]) ->
1129    {'create-subscription',?NETCONF_NOTIF_NAMESPACE_ATTR,
1130     [{stream,[Stream]}] ++
1131	 filter(Filter) ++
1132	 maybe_element(startTime,StartTime) ++
1133	 maybe_element(stopTime,StopTime)}.
1134
1135filter(undefined) ->
1136    [];
1137filter({xpath,Filter}) when ?is_string(Filter) ->
1138    [{filter,[{type,"xpath"},{select, Filter}],[]}];
1139filter(Filter) when is_list(Filter) ->
1140    [{filter,[{type,"subtree"}],Filter}];
1141filter(Filter) ->
1142    filter([Filter]).
1143
1144maybe_element(_,undefined) ->
1145    [];
1146maybe_element(Tag,Value) ->
1147    [{Tag,[Value]}].
1148
1149%%%-----------------------------------------------------------------
1150%%% Send XML data to server
1151do_send_rpc(PendingOp,SimpleXml,Timeout,Caller,
1152	    #state{connection=Connection,msg_id=MsgId,pending=Pending} = State) ->
1153    case do_send_rpc(Connection, MsgId, SimpleXml) of
1154	ok ->
1155	    {Ref,TRef} = set_request_timer(Timeout),
1156	    {noreply, State#state{msg_id=MsgId+1,
1157				  pending=[#pending{tref=TRef,
1158						    ref=Ref,
1159						    msg_id=MsgId,
1160						    op=PendingOp,
1161						    caller=Caller} | Pending]}};
1162	Error ->
1163	    {reply, Error, State#state{msg_id=MsgId+1}}
1164    end.
1165
1166do_send_rpc(Connection, MsgId, SimpleXml) ->
1167    do_send(Connection,
1168	    {rpc,
1169	     [{'message-id',MsgId} | ?NETCONF_NAMESPACE_ATTR],
1170	     [SimpleXml]}).
1171
1172do_send(Connection, SimpleXml) ->
1173    Xml=to_xml_doc(SimpleXml),
1174    ssh_send(Connection, Xml).
1175
1176to_xml_doc(Simple) ->
1177    Prolog = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1178    Xml = unicode:characters_to_binary(
1179	    xmerl:export_simple([Simple],
1180				xmerl_xml,
1181				[#xmlAttribute{name=prolog,
1182					       value=Prolog}])),
1183    <<Xml/binary,?END_TAG/binary>>.
1184
1185%%%-----------------------------------------------------------------
1186%%% Parse and handle received XML data
1187%%% Two buffers are used:
1188%%%   * 'no_end_tag_buff' contains data that is checked and does not
1189%%%     contain any (part of an) end tag.
1190%%%   * 'buff' contains all other saved data - it may or may not
1191%%%     include (a part of) an end tag.
1192%%% The reason for this is to avoid running binary:split/3 multiple
1193%%% times on the same data when it does not contain an end tag. This
1194%%% can be a considerable optimation in the case when a lot of data is
1195%%% received (e.g. when fetching all data from a node) and the data is
1196%%% sent in multiple ssh packages.
1197handle_data(NewData,#state{connection=Connection} = State0) ->
1198    log(Connection,recv,NewData),
1199    NoEndTag0 = State0#state.no_end_tag_buff,
1200    Buff0 = State0#state.buff,
1201    Data = <<Buff0/binary, NewData/binary>>,
1202    case binary:split(Data,?END_TAG,[]) of
1203	[_NoEndTagFound] ->
1204	    NoEndTagSize = case byte_size(Data) of
1205			       Sz when Sz<5 -> 0;
1206			       Sz -> Sz-5
1207			   end,
1208	    <<NoEndTag1:NoEndTagSize/binary,Buff/binary>> = Data,
1209	    NoEndTag = <<NoEndTag0/binary,NoEndTag1/binary>>,
1210	    {noreply, State0#state{no_end_tag_buff=NoEndTag, buff=Buff}};
1211	[FirstMsg0,Buff1] ->
1212	    FirstMsg = remove_initial_nl(<<NoEndTag0/binary,FirstMsg0/binary>>),
1213	    SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}],
1214	    case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of
1215		{ok, Simple, _Thrash} ->
1216		    case decode(Simple, State0#state{no_end_tag_buff= <<>>,
1217						     buff=Buff1}) of
1218			{noreply, #state{buff=Buff} = State} when Buff =/= <<>> ->
1219			    %% Recurse if we have more data in buffer
1220			    handle_data(<<>>, State);
1221			Other ->
1222			    Other
1223		    end;
1224		{fatal_error,_Loc,Reason,_EndTags,_EventState} ->
1225		    ?error(Connection#connection.name,
1226			   [{parse_error,Reason},
1227			    {buffer, Buff0},
1228			    {new_data,NewData}]),
1229		    handle_error(Reason, State0#state{no_end_tag_buff= <<>>,
1230						      buff= <<>>})
1231	    end
1232    end.
1233
1234
1235%% xml does not accept a leading nl and some netconf server add a nl after
1236%% each ?END_TAG, ignore them
1237remove_initial_nl(<<"\n", Data/binary>>) ->
1238    remove_initial_nl(Data);
1239remove_initial_nl(Data) ->
1240    Data.
1241
1242handle_error(Reason, State) ->
1243    Pending1 = case State#state.pending of
1244		   [] -> [];
1245		   Pending ->
1246		       %% Assuming the first request gets the
1247		       %% first answer
1248		       P=#pending{tref=TRef,ref=Ref,caller=Caller} =
1249			   lists:last(Pending),
1250		       cancel_request_timer(Ref,TRef),
1251		       Reason1 = {failed_to_parse_received_data,Reason},
1252		       ct_gen_conn:return(Caller,{error,Reason1}),
1253		       lists:delete(P,Pending)
1254	       end,
1255    {noreply, State#state{pending=Pending1}}.
1256
1257%% Event function for the sax parser. It builds a simple XML structure.
1258%% Care is taken to keep namespace attributes and prefixes as in the original XML.
1259sax_event(Event,_Loc,State) ->
1260    sax_event(Event,State).
1261
1262sax_event({startPrefixMapping, Prefix, Uri},Acc) ->
1263    %% startPrefixMapping will always come immediately before the
1264    %% startElement where the namespace is defined.
1265    [{xmlns,{Prefix,Uri}}|Acc];
1266sax_event({startElement,_Uri,_Name,QN,Attrs},Acc) ->
1267    %% Pick out any namespace attributes inserted due to a
1268    %% startPrefixMapping event.The rest of Acc will then be only
1269    %% elements.
1270    {NsAttrs,NewAcc} = split_attrs_and_elements(Acc,[]),
1271    Tag = qn_to_tag(QN),
1272    [{Tag,NsAttrs ++ parse_attrs(Attrs),[]}|NewAcc];
1273sax_event({endElement,_Uri,_Name,_QN},[{Name,Attrs,Cont},{Parent,PA,PC}|Acc]) ->
1274    [{Parent,PA,[{Name,Attrs,lists:reverse(Cont)}|PC]}|Acc];
1275sax_event(endDocument,[{Tag,Attrs,Cont}]) ->
1276    {Tag,Attrs,lists:reverse(Cont)};
1277sax_event({characters,String},[{Name,Attrs,Cont}|Acc]) ->
1278    [{Name,Attrs,[String|Cont]}|Acc];
1279sax_event(_Event,State) ->
1280    State.
1281
1282split_attrs_and_elements([{xmlns,{Prefix,Uri}}|Rest],Attrs) ->
1283    split_attrs_and_elements(Rest,[{xmlnstag(Prefix),Uri}|Attrs]);
1284split_attrs_and_elements(Elements,Attrs) ->
1285    {Attrs,Elements}.
1286
1287xmlnstag([]) ->
1288    xmlns;
1289xmlnstag(Prefix) ->
1290    list_to_atom("xmlns:"++Prefix).
1291
1292qn_to_tag({[],Name}) ->
1293    list_to_atom(Name);
1294qn_to_tag({Prefix,Name}) ->
1295    list_to_atom(Prefix ++ ":" ++ Name).
1296
1297parse_attrs([{_Uri, [], Name, Value}|Attrs]) ->
1298    [{list_to_atom(Name),Value}|parse_attrs(Attrs)];
1299parse_attrs([{_Uri, Prefix, Name, Value}|Attrs]) ->
1300    [{list_to_atom(Prefix ++ ":" ++ Name),Value}|parse_attrs(Attrs)];
1301parse_attrs([]) ->
1302    [].
1303
1304
1305%%%-----------------------------------------------------------------
1306%%% Decoding of parsed XML data
1307decode({Tag,Attrs,_}=E, #state{connection=Connection,pending=Pending}=State) ->
1308    ConnName = Connection#connection.name,
1309    case get_local_name_atom(Tag) of
1310	'rpc-reply' ->
1311	    case get_msg_id(Attrs) of
1312		undefined ->
1313		    case Pending of
1314			[#pending{msg_id=MsgId}] ->
1315			    ?error(ConnName,[{warning,rpc_reply_missing_msg_id},
1316					       {assuming,MsgId}]),
1317			    decode_rpc_reply(MsgId,E,State);
1318			_ ->
1319			    ?error(ConnName,[{error,rpc_reply_missing_msg_id}]),
1320			    {noreply,State}
1321		    end;
1322		MsgId ->
1323		    decode_rpc_reply(MsgId,E,State)
1324	    end;
1325	hello ->
1326	    case State#state.hello_status of
1327		undefined ->
1328		    case decode_hello(E) of
1329			{ok,SessionId,Capabilities} ->
1330			    {noreply,State#state{session_id = SessionId,
1331						 capabilities = Capabilities,
1332						 hello_status = received}};
1333			{error,Reason} ->
1334			    {noreply,State#state{hello_status = {error,Reason}}}
1335		    end;
1336		#pending{tref=TRef,ref=Ref,caller=Caller} ->
1337		    cancel_request_timer(Ref,TRef),
1338		    case decode_hello(E) of
1339			{ok,SessionId,Capabilities} ->
1340			    ct_gen_conn:return(Caller,ok),
1341			    {noreply,State#state{session_id = SessionId,
1342						 capabilities = Capabilities,
1343						 hello_status = done}};
1344			{error,Reason} ->
1345			    ct_gen_conn:return(Caller,{error,Reason}),
1346			    {stop,State#state{hello_status={error,Reason}}}
1347		    end;
1348		Other ->
1349		    ?error(ConnName,[{got_unexpected_hello,E},
1350				       {hello_status,Other}]),
1351		    {noreply,State}
1352	    end;
1353	notification ->
1354	    EventReceiver = State#state.event_receiver,
1355	    EventReceiver ! E,
1356	    {noreply,State};
1357	Other ->
1358	    %% Result of send/2, when not sending an rpc request - or
1359	    %% if netconf server sends noise. Can handle this only if
1360	    %% there is just one pending that matches (i.e. has
1361	    %% undefined msg_id and op)
1362	    case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
1363		[#pending{tref=TRef,ref=Ref,caller=Caller}] ->
1364		    cancel_request_timer(Ref,TRef),
1365		    ct_gen_conn:return(Caller,E),
1366		    {noreply,State#state{pending=[]}};
1367		_ ->
1368		    ?error(ConnName,[{got_unexpected_msg,Other},
1369				       {expecting,Pending}]),
1370		    {noreply,State}
1371	    end
1372
1373    end.
1374
1375get_msg_id(Attrs) ->
1376    case lists:keyfind('message-id',1,Attrs) of
1377	{_,Str} ->
1378	    list_to_integer(Str);
1379	false ->
1380	    undefined
1381    end.
1382
1383decode_rpc_reply(MsgId,{_,Attrs,Content0}=E,#state{pending=Pending} = State) ->
1384    case lists:keytake(MsgId,#pending.msg_id,Pending) of
1385	{value, #pending{tref=TRef,ref=Ref,op=Op,caller=Caller}, Pending1} ->
1386	    cancel_request_timer(Ref,TRef),
1387	    Content = forward_xmlns_attr(Attrs,Content0),
1388	    {CallerReply,{ServerReply,State2}} =
1389		do_decode_rpc_reply(Op,Content,State#state{pending=Pending1}),
1390	    ct_gen_conn:return(Caller,CallerReply),
1391	    {ServerReply,State2};
1392	false ->
1393	    %% Result of send/2, when receiving a correct
1394	    %% rpc-reply. Can handle this only if there is just one
1395	    %% pending that matches (i.e. has undefined msg_id and op)
1396	    case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
1397		[#pending{tref=TRef,
1398			  ref=Ref,
1399			  msg_id=undefined,
1400			  op=undefined,
1401			  caller=Caller}] ->
1402		    cancel_request_timer(Ref,TRef),
1403		    ct_gen_conn:return(Caller,E),
1404		    {noreply,State#state{pending=[]}};
1405		_ ->
1406		    ConnName = (State#state.connection)#connection.name,
1407		    ?error(ConnName,[{got_unexpected_msg_id,MsgId},
1408						   {expecting,Pending}]),
1409		    {noreply,State}
1410	    end
1411    end.
1412
1413do_decode_rpc_reply(Op,Result,State)
1414  when Op==lock; Op==unlock; Op==edit_config; Op==delete_config;
1415       Op==copy_config; Op==kill_session ->
1416    {decode_ok(Result),{noreply,State}};
1417do_decode_rpc_reply(Op,Result,State)
1418  when Op==get; Op==get_config; Op==action ->
1419    {decode_data(Result),{nore

Large files files are truncated, but you can click here to view the full file