PageRenderTime 11ms CodeModel.GetById 4ms app.highlight 178ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/common_test/src/ct_netconfc.erl

https://github.com/bsmr-erlang/otp
Erlang | 1820 lines | 1391 code | 170 blank | 259 comment | 6 complexity | 53e6661b222f4ce27388f9089d0d6927 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() | [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() | [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() | [simple_xml()],
 612      OptParams :: [simple_xml()],
 613      Timeout :: timeout(),
 614      Result :: ok | {error,error_reason()}.
 615edit_config(Client, Target, Config, OptParams, Timeout) when not is_list(Config)->
 616    edit_config(Client, Target, [Config], OptParams, Timeout);
 617edit_config(Client, Target, Config, OptParams, Timeout) ->
 618    call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
 619
 620
 621%%----------------------------------------------------------------------
 622%% Send a 'delete-config' request.
 623-spec delete_config(Client, Target) -> Result when
 624      Client :: client(),
 625      Target :: startup | candidate,
 626      Result :: ok | {error,error_reason()}.
 627delete_config(Client, Target) ->
 628    delete_config(Client, Target, ?DEFAULT_TIMEOUT).
 629
 630-spec delete_config(Client, Target, Timeout) -> Result when
 631      Client :: client(),
 632      Target :: startup | candidate,
 633      Timeout :: timeout(),
 634      Result :: ok | {error,error_reason()}.
 635delete_config(Client, Target, Timeout) when Target == startup;
 636					    Target == candidate ->
 637    call(Client,{send_rpc_op, delete_config, [Target], Timeout}).
 638
 639%%----------------------------------------------------------------------
 640%% Send a 'copy-config' request.
 641-spec copy_config(Client, Target, Source) -> Result when
 642      Client :: client(),
 643      Target :: netconf_db(),
 644      Source :: netconf_db(),
 645      Result :: ok | {error,error_reason()}.
 646copy_config(Client, Source, Target) ->
 647    copy_config(Client, Source, Target, ?DEFAULT_TIMEOUT).
 648
 649-spec copy_config(Client, Target, Source, Timeout) -> Result when
 650      Client :: client(),
 651      Target :: netconf_db(),
 652      Source :: netconf_db(),
 653      Timeout :: timeout(),
 654      Result :: ok | {error,error_reason()}.
 655copy_config(Client, Target, Source, Timeout) ->
 656    call(Client,{send_rpc_op, copy_config, [Target, Source], Timeout}).
 657
 658%%----------------------------------------------------------------------
 659%% Execute an action.
 660-spec action(Client, Action) -> Result when
 661      Client :: client(),
 662      Action :: simple_xml(),
 663      Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
 664action(Client,Action) ->
 665    action(Client,Action,?DEFAULT_TIMEOUT).
 666
 667-spec action(Client, Action, Timeout) -> Result when
 668      Client :: client(),
 669      Action :: simple_xml(),
 670      Timeout :: timeout(),
 671      Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
 672action(Client,Action,Timeout) ->
 673    call(Client,{send_rpc_op, action, [Action], Timeout}).
 674
 675%%----------------------------------------------------------------------
 676%% Send a 'create-subscription' request
 677%% See RFC5277, NETCONF Event Notifications
 678-spec create_subscription(Client) -> Result when
 679      Client :: client(),
 680      Result :: ok | {error,error_reason()}.
 681create_subscription(Client) ->
 682    create_subscription(Client,?DEFAULT_STREAM,?DEFAULT_TIMEOUT).
 683
 684-spec create_subscription(Client, Stream | Filter | Timeout) -> Result when
 685      Client :: client(),
 686      Stream :: stream_name(),
 687      Filter :: simple_xml() | [simple_xml()],
 688      Timeout :: timeout(),
 689      Result :: ok | {error,error_reason()}.
 690create_subscription(Client,Timeout)
 691  when ?is_timeout(Timeout) ->
 692    create_subscription(Client,?DEFAULT_STREAM,Timeout);
 693create_subscription(Client,Stream)
 694  when ?is_string(Stream) ->
 695    create_subscription(Client,Stream,?DEFAULT_TIMEOUT);
 696create_subscription(Client,Filter)
 697  when ?is_filter(Filter) ->
 698    create_subscription(Client,?DEFAULT_STREAM,Filter,
 699			?DEFAULT_TIMEOUT).
 700
 701create_subscription(Client,Stream,Timeout)
 702  when ?is_string(Stream) andalso
 703       ?is_timeout(Timeout) ->
 704    call(Client,{send_rpc_op,{create_subscription,self()},
 705		 [Stream,undefined,undefined,undefined],
 706		 Timeout});
 707create_subscription(Client,StartTime,StopTime)
 708  when ?is_string(StartTime) andalso
 709       ?is_string(StopTime) ->
 710    create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,
 711			?DEFAULT_TIMEOUT);
 712create_subscription(Client,Filter,Timeout)
 713  when ?is_filter(Filter) andalso
 714       ?is_timeout(Timeout) ->
 715    create_subscription(Client,?DEFAULT_STREAM,Filter,Timeout);
 716create_subscription(Client,Stream,Filter)
 717  when ?is_string(Stream) andalso
 718       ?is_filter(Filter) ->
 719    create_subscription(Client,Stream,Filter,?DEFAULT_TIMEOUT).
 720
 721create_subscription(Client,StartTime,StopTime,Timeout)
 722  when ?is_string(StartTime) andalso
 723       ?is_string(StopTime) andalso
 724       ?is_timeout(Timeout) ->
 725    create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,Timeout);
 726create_subscription(Client,Stream,StartTime,StopTime)
 727  when ?is_string(Stream) andalso
 728       ?is_string(StartTime) andalso
 729       ?is_string(StopTime) ->
 730    create_subscription(Client,Stream,StartTime,StopTime,?DEFAULT_TIMEOUT);
 731create_subscription(Client,Filter,StartTime,StopTime)
 732  when ?is_filter(Filter) andalso
 733       ?is_string(StartTime) andalso
 734       ?is_string(StopTime) ->
 735    create_subscription(Client,?DEFAULT_STREAM,Filter,
 736			StartTime,StopTime,?DEFAULT_TIMEOUT);
 737create_subscription(Client,Stream,Filter,Timeout)
 738  when ?is_string(Stream) andalso
 739       ?is_filter(Filter) andalso
 740       ?is_timeout(Timeout) ->
 741    call(Client,{send_rpc_op,{create_subscription,self()},
 742		 [Stream,Filter,undefined,undefined],
 743		 Timeout}).
 744
 745-spec create_subscription(Client, Stream, StartTime, StopTime, Timeout) ->
 746				 Result when
 747      Client :: client(),
 748      Stream :: stream_name(),
 749      StartTime :: xs_datetime(),
 750      StopTime :: xs_datetime(),
 751      Timeout :: timeout(),
 752      Result :: ok | {error,error_reason()};
 753                         (Client, Stream, Filter,StartTime, StopTime) ->
 754				 Result when
 755      Client :: client(),
 756      Stream :: stream_name(),
 757      Filter :: simple_xml() | [simple_xml()],
 758      StartTime :: xs_datetime(),
 759      StopTime :: xs_datetime(),
 760      Result :: ok | {error,error_reason()}.
 761create_subscription(Client,Stream,StartTime,StopTime,Timeout)
 762  when ?is_string(Stream) andalso
 763       ?is_string(StartTime) andalso
 764       ?is_string(StopTime) andalso
 765       ?is_timeout(Timeout) ->
 766    call(Client,{send_rpc_op,{create_subscription,self()},
 767		 [Stream,undefined,StartTime,StopTime],
 768		 Timeout});
 769create_subscription(Client,Stream,Filter,StartTime,StopTime)
 770  when ?is_string(Stream) andalso
 771       ?is_filter(Filter) andalso
 772       ?is_string(StartTime) andalso
 773       ?is_string(StopTime) ->
 774    create_subscription(Client,Stream,Filter,StartTime,StopTime,?DEFAULT_TIMEOUT).
 775
 776-spec create_subscription(Client, Stream, Filter,StartTime, StopTime, Timeout) ->
 777				 Result when
 778      Client :: client(),
 779      Stream :: stream_name(),
 780      Filter :: simple_xml() | [simple_xml()],
 781      StartTime :: xs_datetime(),
 782      StopTime :: xs_datetime(),
 783      Timeout :: timeout(),
 784      Result :: ok | {error,error_reason()}.
 785create_subscription(Client,Stream,Filter,StartTime,StopTime,Timeout) ->
 786    call(Client,{send_rpc_op,{create_subscription, self()},
 787		 [Stream,Filter,StartTime,StopTime],
 788		 Timeout}).
 789
 790%%----------------------------------------------------------------------
 791%% Send a request to get the given event streams
 792%% See RFC5277, NETCONF Event Notifications
 793-spec get_event_streams(Client)
 794		       -> Result when
 795      Client :: client(),
 796      Result :: {ok,streams()} | {error,error_reason()}.
 797get_event_streams(Client) ->
 798    get_event_streams(Client,[],?DEFAULT_TIMEOUT).
 799
 800-spec get_event_streams(Client, Timeout)
 801		       -> Result when
 802      Client :: client(),
 803      Timeout :: timeout(),
 804      Result :: {ok,streams()} | {error,error_reason()};
 805                       (Client, Streams) -> Result when
 806      Client :: client(),
 807      Streams :: [stream_name()],
 808      Result :: {ok,streams()} | {error,error_reason()}.
 809get_event_streams(Client,Timeout) when is_integer(Timeout); Timeout==infinity ->
 810    get_event_streams(Client,[],Timeout);
 811get_event_streams(Client,Streams) when is_list(Streams) ->
 812    get_event_streams(Client,Streams,?DEFAULT_TIMEOUT).
 813
 814-spec get_event_streams(Client, Streams, Timeout)
 815		       -> Result when
 816      Client :: client(),
 817      Streams :: [stream_name()],
 818      Timeout :: timeout(),
 819      Result :: {ok,streams()} | {error,error_reason()}.
 820get_event_streams(Client,Streams,Timeout) ->
 821    call(Client,{get_event_streams,Streams,Timeout}).
 822
 823
 824%%----------------------------------------------------------------------
 825%% Send a 'close-session' request
 826-spec close_session(Client) -> Result when
 827      Client :: client(),
 828      Result :: ok | {error,error_reason()}.
 829close_session(Client) ->
 830    close_session(Client, ?DEFAULT_TIMEOUT).
 831
 832-spec close_session(Client, Timeout) -> Result when
 833      Client :: client(),
 834      Timeout :: timeout(),
 835      Result :: ok | {error,error_reason()}.
 836close_session(Client, Timeout) ->
 837    call(Client,{send_rpc_op, close_session, [], Timeout}, true).
 838
 839
 840%%----------------------------------------------------------------------
 841%% Send a 'kill-session' request
 842-spec kill_session(Client, SessionId) -> Result when
 843      Client :: client(),
 844      SessionId :: pos_integer(),
 845      Result :: ok | {error,error_reason()}.
 846kill_session(Client, SessionId) ->
 847    kill_session(Client, SessionId, ?DEFAULT_TIMEOUT).
 848
 849-spec kill_session(Client, SessionId, Timeout) -> Result when
 850      Client :: client(),
 851      SessionId :: pos_integer(),
 852      Timeout :: timeout(),
 853      Result :: ok | {error,error_reason()}.
 854kill_session(Client, SessionId, Timeout) ->
 855    call(Client,{send_rpc_op, kill_session, [SessionId], Timeout}).
 856
 857
 858%%----------------------------------------------------------------------
 859%% Callback functions
 860%%----------------------------------------------------------------------
 861
 862init(_KeyOrName,{CM,{Host,Port}},Options) ->
 863    case ssh_channel(#connection{reference=CM,host=Host,port=Port},Options) of
 864        {ok,Connection} ->
 865	    {ok, CM, #state{connection = Connection}};
 866	{error,Reason}->
 867	    {error,Reason}
 868    end;
 869init(_KeyOrName,{_Host,_Port},Options) when Options#options.type==connection ->
 870    case ssh_connect(Options) of
 871        {ok, Connection} ->
 872	    ConnPid = Connection#connection.reference,
 873            {ok, ConnPid, #state{connection = Connection}};
 874        Error ->
 875            Error
 876    end;
 877init(_KeyOrName,{_Host,_Port},Options) ->
 878    case ssh_open(Options) of
 879	{ok, Connection} ->
 880	    {ConnPid,_} = Connection#connection.reference,
 881	    {ok, ConnPid, #state{connection = Connection}};
 882	{error,Reason}->
 883	    {error,Reason}
 884    end.
 885
 886
 887terminate(_, #state{connection=Connection}) ->
 888    ssh_close(Connection),
 889    ok.
 890
 891handle_msg({hello, Options, Timeout}, From,
 892	   #state{connection=Connection,hello_status=HelloStatus} = State) ->
 893    case do_send(Connection, client_hello(Options)) of
 894	ok ->
 895	    case HelloStatus of
 896		undefined ->
 897		    {Ref,TRef} = set_request_timer(Timeout),
 898		    {noreply, State#state{hello_status=#pending{tref=TRef,
 899								ref=Ref,
 900								caller=From}}};
 901		received ->
 902		    {reply, ok, State#state{hello_status=done}};
 903		{error,Reason} ->
 904		    {stop, {error,Reason}, State}
 905	    end;
 906	Error ->
 907	    {stop, Error, State}
 908    end;
 909handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
 910    Reply =
 911        case Connection#connection.reference of
 912            {_,_} -> {error,not_an_ssh_connection};
 913            CM -> {ok,{CM,{Connection#connection.host,
 914                           Connection#connection.port}}}
 915        end,
 916    {reply, Reply, State};
 917handle_msg(_, _From, #state{session_id=undefined} = State) ->
 918    %% Hello is not yet excanged - this shall never happen
 919    {reply,{error,waiting_for_hello},State};
 920handle_msg(get_capabilities, _From, #state{capabilities = Caps} = State) ->
 921    {reply, Caps, State};
 922handle_msg(get_session_id, _From, #state{session_id = Id} = State) ->
 923    {reply, Id, State};
 924handle_msg({send, Timeout, SimpleXml}, From,
 925	    #state{connection=Connection,pending=Pending} = State) ->
 926    case do_send(Connection, SimpleXml) of
 927	ok ->
 928	    {Ref,TRef} = set_request_timer(Timeout),
 929	    {noreply, State#state{pending=[#pending{tref=TRef,
 930						    ref=Ref,
 931						    caller=From} | Pending]}};
 932	Error ->
 933	    {reply, Error, State}
 934    end;
 935handle_msg({send_rpc, SimpleXml, Timeout}, From, State) ->
 936    do_send_rpc(undefined, SimpleXml, Timeout, From, State);
 937handle_msg({send_rpc_op, Op, Data, Timeout}, From, State) ->
 938    SimpleXml = encode_rpc_operation(Op,Data),
 939    do_send_rpc(Op, SimpleXml, Timeout, From, State);
 940handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
 941    Filter = {netconf,?NETMOD_NOTIF_NAMESPACE_ATTR,
 942             [{streams,[{stream,[{name,[Name]}]} || Name <- Streams]}]},
 943    SimpleXml = encode_rpc_operation(get,[Filter]),
 944    do_send_rpc(Op, SimpleXml, Timeout, From, State).
 945
 946handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
 947    ssh_connection:adjust_window(CM,Ch,size(Data)),
 948    handle_data(Data, State);
 949handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
 950    %% _SshCloseMsg can probably be one of
 951    %% {eof,Ch}
 952    %% {exit_status,Ch,Status}
 953    %% {exit_signal,Ch,ExitSignal,ErrorMsg,LanguageString}
 954    %% {signal,Ch,Signal}
 955
 956    %% This might e.g. happen if the server terminates the connection,
 957    %% as in kill-session (or if ssh:close is called from somewhere
 958    %% unexpected).
 959
 960    %%! Log this??
 961    %%! Currently the log will say that the client closed the
 962    %%! connection - due to terminate/2
 963
 964    {stop, State};
 965handle_msg({Ref,timeout},
 966	   #state{hello_status=#pending{ref=Ref,caller=Caller}} = State) ->
 967    ct_gen_conn:return(Caller,{error,{hello_session_failed,timeout}}),
 968    {stop,State#state{hello_status={error,timeout}}};
 969handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
 970    {value,#pending{op=Op,caller=Caller},Pending1} =
 971	lists:keytake(Ref,#pending.ref,Pending),
 972    ct_gen_conn:return(Caller,{error,timeout}),
 973    R = case Op of
 974	    close_session -> stop;
 975	    _ -> noreply
 976	end,
 977    %% Halfhearted try to get in correct state, this matches
 978    %% the implementation before this patch
 979    {R,State#state{pending=Pending1, no_end_tag_buff= <<>>, buff= <<>>}}.
 980
 981%% Called by ct_util_server to close registered connections before terminate.
 982close(Client) ->
 983    case get_handle(Client) of
 984	{ok,Pid} ->
 985	    case ct_gen_conn:stop(Pid) of
 986		{error,{process_down,Pid,noproc}} ->
 987		    {error,already_closed};
 988		Result ->
 989		    Result
 990	    end;
 991	Error ->
 992	    Error
 993    end.
 994
 995
 996%%----------------------------------------------------------------------
 997%% Internal functions
 998%%----------------------------------------------------------------------
 999call(Client, Msg) ->
1000    call(Client, Msg, infinity, false).
1001call(Client, Msg, Timeout) when is_integer(Timeout); Timeout==infinity ->
1002    call(Client, Msg, Timeout, false);
1003call(Client, Msg, WaitStop) when is_boolean(WaitStop) ->
1004    call(Client, Msg, infinity, WaitStop).
1005call(Client, Msg, Timeout, WaitStop) ->
1006    case get_handle(Client) of
1007	{ok,Pid} ->
1008	    case ct_gen_conn:call(Pid,Msg,Timeout) of
1009		{error,{process_down,Pid,noproc}} ->
1010		    {error,no_such_client};
1011		{error,{process_down,Pid,normal}} when WaitStop ->
1012		    %% This will happen when server closes connection
1013		    %% before client received rpc-reply on
1014		    %% close-session.
1015		    ok;
1016		{error,{process_down,Pid,normal}} ->
1017		    {error,closed};
1018		{error,{process_down,Pid,Reason}} ->
1019		    {error,{closed,Reason}};
1020		Other when WaitStop ->
1021		    MRef = erlang:monitor(process,Pid),
1022		    receive
1023			{'DOWN',MRef,process,Pid,Normal} when Normal==normal;
1024							      Normal==noproc ->
1025			    Other;
1026			{'DOWN',MRef,process,Pid,Reason} ->
1027			    {error,{{closed,Reason},Other}}
1028		    after Timeout ->
1029			    erlang:demonitor(MRef, [flush]),
1030			    {error,{timeout,Other}}
1031		    end;
1032		Other ->
1033		    Other
1034	    end;
1035	Error ->
1036	    Error
1037    end.
1038
1039get_handle(Client) when is_pid(Client) ->
1040    {ok,Client};
1041get_handle(Client) ->
1042    case ct_util:get_connection(Client, ?MODULE) of
1043	{ok,{Pid,_}} ->
1044	    {ok,Pid};
1045	{error,no_registered_connection} ->
1046	    {error,{no_connection_found,Client}};
1047	Error ->
1048	    Error
1049    end.
1050
1051check_options(OptList,Options) ->
1052    check_options(OptList,undefined,undefined,Options).
1053
1054check_options([], undefined, _Port, _Options) ->
1055    {error, no_host_address};
1056check_options([], _Host, undefined, _Options) ->
1057    {error, no_port};
1058check_options([], Host, Port, Options) ->
1059    {Host,Port,Options};
1060check_options([{ssh, Host}|T], _, Port, Options) ->
1061    check_options(T, Host, Port, Options#options{host=Host});
1062check_options([{port,Port}|T], Host, _, Options) ->
1063    check_options(T, Host, Port, Options#options{port=Port});
1064check_options([{timeout, Timeout}|T], Host, Port, Options)
1065  when is_integer(Timeout); Timeout==infinity ->
1066    check_options(T, Host, Port, Options#options{timeout = Timeout});
1067check_options([{timeout, _} = Opt|_T], _Host, _Port, _Options) ->
1068    {error, {invalid_option, Opt}};
1069check_options([Opt|T], Host, Port, #options{ssh=SshOpts}=Options) ->
1070    %% Option verified by ssh
1071    check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}).
1072
1073check_session_options([],Options) ->
1074    {ok,Options};
1075check_session_options([{timeout, Timeout}|T], Options)
1076  when is_integer(Timeout); Timeout==infinity ->
1077    check_session_options(T, Options#options{timeout = Timeout});
1078check_session_options([Opt|_T], _Options) ->
1079    {error, {invalid_option, Opt}}.
1080
1081
1082%%%-----------------------------------------------------------------
1083set_request_timer(infinity) ->
1084    {undefined,undefined};
1085set_request_timer(T) ->
1086    Ref = make_ref(),
1087    {ok,TRef} = timer:send_after(T,{Ref,timeout}),
1088    {Ref,TRef}.
1089
1090%%%-----------------------------------------------------------------
1091cancel_request_timer(undefined,undefined) ->
1092    ok;
1093cancel_request_timer(Ref,TRef) ->
1094    _ = timer:cancel(TRef),
1095    receive {Ref,timeout} -> ok
1096    after 0 -> ok
1097    end.
1098
1099%%%-----------------------------------------------------------------
1100client_hello(Options) when is_list(Options) ->
1101    UserCaps = [{capability, UserCap} ||
1102		   {capability, UserCap} <- Options,
1103		   is_list(hd(UserCap))],
1104    {hello, ?NETCONF_NAMESPACE_ATTR,
1105     [{capabilities,
1106       [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}|
1107	UserCaps]}]}.
1108
1109%%%-----------------------------------------------------------------
1110
1111encode_rpc_operation(Lock,[Target]) when Lock==lock; Lock==unlock ->
1112    {Lock,[{target,[Target]}]};
1113encode_rpc_operation(get,[Filter]) ->
1114    {get,filter(Filter)};
1115encode_rpc_operation(get_config,[Source,Filter]) ->
1116    {'get-config',[{source,[Source]}] ++ filter(Filter)};
1117encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
1118    {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,Config}]};
1119encode_rpc_operation(delete_config,[Target]) ->
1120    {'delete-config',[{target,[Target]}]};
1121encode_rpc_operation(copy_config,[Target,Source]) ->
1122    {'copy-config',[{target,[Target]},{source,[Source]}]};
1123encode_rpc_operation(action,[Action]) ->
1124    {action,?ACTION_NAMESPACE_ATTR,[{data,[Action]}]};
1125encode_rpc_operation(kill_session,[SessionId]) ->
1126    {'kill-session',[{'session-id',[integer_to_list(SessionId)]}]};
1127encode_rpc_operation(close_session,[]) ->
1128    'close-session';
1129encode_rpc_operation({create_subscription,_},
1130		     [Stream,Filter,StartTime,StopTime]) ->
1131    {'create-subscription',?NETCONF_NOTIF_NAMESPACE_ATTR,
1132     [{stream,[Stream]}] ++
1133	 filter(Filter) ++
1134	 maybe_element(startTime,StartTime) ++
1135	 maybe_element(stopTime,StopTime)}.
1136
1137filter(undefined) ->
1138    [];
1139filter({xpath,Filter}) when ?is_string(Filter) ->
1140    [{filter,[{type,"xpath"},{select, Filter}],[]}];
1141filter(Filter) when is_list(Filter) ->
1142    [{filter,[{type,"subtree"}],Filter}];
1143filter(Filter) ->
1144    filter([Filter]).
1145
1146maybe_element(_,undefined) ->
1147    [];
1148maybe_element(Tag,Value) ->
1149    [{Tag,[Value]}].
1150
1151%%%-----------------------------------------------------------------
1152%%% Send XML data to server
1153do_send_rpc(PendingOp,SimpleXml,Timeout,Caller,
1154	    #state{connection=Connection,msg_id=MsgId,pending=Pending} = State) ->
1155    case do_send_rpc(Connection, MsgId, SimpleXml) of
1156	ok ->
1157	    {Ref,TRef} = set_request_timer(Timeout),
1158	    {noreply, State#state{msg_id=MsgId+1,
1159				  pending=[#pending{tref=TRef,
1160						    ref=Ref,
1161						    msg_id=MsgId,
1162						    op=PendingOp,
1163						    caller=Caller} | Pending]}};
1164	Error ->
1165	    {reply, Error, State#state{msg_id=MsgId+1}}
1166    end.
1167
1168do_send_rpc(Connection, MsgId, SimpleXml) ->
1169    do_send(Connection,
1170	    {rpc,
1171	     [{'message-id',MsgId} | ?NETCONF_NAMESPACE_ATTR],
1172	     [SimpleXml]}).
1173
1174do_send(Connection, SimpleXml) ->
1175    Xml=to_xml_doc(SimpleXml),
1176    ssh_send(Connection, Xml).
1177
1178to_xml_doc(Simple) ->
1179    Prolog = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1180    Xml = unicode:characters_to_binary(
1181	    xmerl:export_simple([Simple],
1182				xmerl_xml,
1183				[#xmlAttribute{name=prolog,
1184					       value=Prolog}])),
1185    <<Xml/binary,?END_TAG/binary>>.
1186
1187%%%-----------------------------------------------------------------
1188%%% Parse and handle received XML data
1189%%% Two buffers are used:
1190%%%   * 'no_end_tag_buff' contains data that is checked and does not
1191%%%     contain any (part of an) end tag.
1192%%%   * 'buff' contains all other saved data - it may or may not
1193%%%     include (a part of) an end tag.
1194%%% The reason for this is to avoid running binary:split/3 multiple
1195%%% times on the same data when it does not contain an end tag. This
1196%%% can be a considerable optimation in the case when a lot of data is
1197%%% received (e.g. when fetching all data from a node) and the data is
1198%%% sent in multiple ssh packages.
1199handle_data(NewData,#state{connection=Connection} = State0) ->
1200    log(Connection,recv,NewData),
1201    NoEndTag0 = State0#state.no_end_tag_buff,
1202    Buff0 = State0#state.buff,
1203    Data = <<Buff0/binary, NewData/binary>>,
1204    case binary:split(Data,?END_TAG,[]) of
1205	[_NoEndTagFound] ->
1206	    NoEndTagSize = case byte_size(Data) of
1207			       Sz when Sz<5 -> 0;
1208			       Sz -> Sz-5
1209			   end,
1210	    <<NoEndTag1:NoEndTagSize/binary,Buff/binary>> = Data,
1211	    NoEndTag = <<NoEndTag0/binary,NoEndTag1/binary>>,
1212	    {noreply, State0#state{no_end_tag_buff=NoEndTag, buff=Buff}};
1213	[FirstMsg0,Buff1] ->
1214	    FirstMsg = remove_initial_nl(<<NoEndTag0/binary,FirstMsg0/binary>>),
1215	    SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}],
1216	    case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of
1217		{ok, Simple, _Thrash} ->
1218		    case decode(Simple, State0#state{no_end_tag_buff= <<>>,
1219						     buff=Buff1}) of
1220			{noreply, #state{buff=Buff} = State} when Buff =/= <<>> ->
1221			    %% Recurse if we have more data in buffer
1222			    handle_data(<<>>, State);
1223			Other ->
1224			    Other
1225		    end;
1226		{fatal_error,_Loc,Reason,_EndTags,_EventState} ->
1227		    ?error(Connection#connection.name,
1228			   [{parse_error,Reason},
1229			    {buffer, Buff0},
1230			    {new_data,NewData}]),
1231		    handle_error(Reason, State0#state{no_end_tag_buff= <<>>,
1232						      buff= <<>>})
1233	    end
1234    end.
1235
1236
1237%% xml does not accept a leading nl and some netconf server add a nl after
1238%% each ?END_TAG, ignore them
1239remove_initial_nl(<<"\n", Data/binary>>) ->
1240    remove_initial_nl(Data);
1241remove_initial_nl(Data) ->
1242    Data.
1243
1244handle_error(Reason, State) ->
1245    Pending1 = case State#state.pending of
1246		   [] -> [];
1247		   Pending ->
1248		       %% Assuming the first request gets the
1249		       %% first answer
1250		       P=#pending{tref=TRef,ref=Ref,caller=Caller} =
1251			   lists:last(Pending),
1252		       cancel_request_timer(Ref,TRef),
1253		       Reason1 = {failed_to_parse_received_data,Reason},
1254		       ct_gen_conn:return(Caller,{error,Reason1}),
1255		       lists:delete(P,Pending)
1256	       end,
1257    {noreply, State#state{pending=Pending1}}.
1258
1259%% Event function for the sax parser. It builds a simple XML structure.
1260%% Care is taken to keep namespace attributes and prefixes as in the original XML.
1261sax_event(Event,_Loc,State) ->
1262    sax_event(Event,State).
1263
1264sax_event({startPrefixMapping, Prefix, Uri},Acc) ->
1265    %% startPrefixMapping will always come immediately before the
1266    %% startElement where the namespace is defined.
1267    [{xmlns,{Prefix,Uri}}|Acc];
1268sax_event({startElement,_Uri,_Name,QN,Attrs},Acc) ->
1269    %% Pick out any namespace attributes inserted due to a
1270    %% startPrefixMapping event.The rest of Acc will then be only
1271    %% elements.
1272    {NsAttrs,NewAcc} = split_attrs_and_elements(Acc,[]),
1273    Tag = qn_to_tag(QN),
1274    [{Tag,NsAttrs ++ parse_attrs(Attrs),[]}|NewAcc];
1275sax_event({endElement,_Uri,_Name,_QN},[{Name,Attrs,Cont},{Parent,PA,PC}|Acc]) ->
1276    [{Parent,PA,[{Name,Attrs,lists:reverse(Cont)}|PC]}|Acc];
1277sax_event(endDocument,[{Tag,Attrs,Cont}]) ->
1278    {Tag,Attrs,lists:reverse(Cont)};
1279sax_event({characters,String},[{Name,Attrs,Cont}|Acc]) ->
1280    [{Name,Attrs,[String|Cont]}|Acc];
1281sax_event(_Event,State) ->
1282    State.
1283
1284split_attrs_and_elements([{xmlns,{Prefix,Uri}}|Rest],Attrs) ->
1285    split_attrs_and_elements(Rest,[{xmlnstag(Prefix),Uri}|Attrs]);
1286split_attrs_and_elements(Elements,Attrs) ->
1287    {Attrs,Elements}.
1288
1289xmlnstag([]) ->
1290    xmlns;
1291xmlnstag(Prefix) ->
1292    list_to_atom("xmlns:"++Prefix).
1293
1294qn_to_tag({[],Name}) ->
1295    list_to_atom(Name);
1296qn_to_tag({Prefix,Name}) ->
1297    list_to_atom(Prefix ++ ":" ++ Name).
1298
1299parse_attrs([{_Uri, [], Name, Value}|Attrs]) ->
1300    [{list_to_atom(Name),Value}|parse_attrs(Attrs)];
1301parse_attrs([{_Uri, Prefix, Name, Value}|Attrs]) ->
1302    [{list_to_atom(Prefix ++ ":" ++ Name),Value}|parse_attrs(Attrs)];
1303parse_attrs([]) ->
1304    [].
1305
1306
1307%%%-----------------------------------------------------------------
1308%%% Decoding of parsed XML data
1309decode({Tag,Attrs,_}=E, #state{connection=Connection,pending=Pending}=State) ->
1310    ConnName = Connection#connection.name,
1311    case get_local_name_atom(Tag) of
1312	'rpc-reply' ->
1313	    case get_msg_id(Attrs) of
1314		undefined ->
1315		    case Pending of
1316			[#pending{msg_id=MsgId}] ->
1317			    ?error(ConnName,[{warning,rpc_reply_missing_msg_id},
1318					       {assuming,MsgId}]),
1319			    decode_rpc_reply(MsgId,E,State);
1320			_ ->
1321			    ?error(ConnName,[{error,rpc_reply_missing_msg_id}]),
1322			    {noreply,State}
1323		    end;
1324		MsgId ->
1325		    decode_rpc_reply(MsgId,E,State)
1326	    end;
1327	hello ->
1328	    case State#state.hello_status of
1329		undefined ->
1330		    case decode_hello(E) of
1331			{ok,SessionId,Capabilities} ->
1332			    {noreply,State#state{session_id = SessionId,
1333						 capabilities = Capabilities,
1334						 hello_status = received}};
1335			{error,Reason} ->
1336			    {noreply,State#state{hello_status = {error,Reason}}}
1337		    end;
1338		#pending{tref=TRef,ref=Ref,caller=Caller} ->
1339		    cancel_request_timer(Ref,TRef),
1340		    case decode_hello(E) of
1341			{ok,SessionId,Capabilities} ->
1342			    ct_gen_conn:return(Caller,ok),
1343			    {noreply,State#state{session_id = SessionId,
1344						 capabilities = Capabilities,
1345						 hello_status = done}};
1346			{error,Reason} ->
1347			    ct_gen_conn:return(Caller,{error,Reason}),
1348			    {stop,State#state{hello_status={error,Reason}}}
1349		    end;
1350		Other ->
1351		    ?error(ConnName,[{got_unexpected_hello,E},
1352				       {hello_status,Other}]),
1353		    {noreply,State}
1354	    end;
1355	notification ->
1356	    EventReceiver = State#state.event_receiver,
1357	    EventReceiver ! E,
1358	    {noreply,State};
1359	Other ->
1360	    %% Result of send/2, when not sending an rpc request - or
1361	    %% if netconf server sends noise. Can handle this only if
1362	    %% there is just one pending that matches (i.e. has
1363	    %% undefined msg_id and op)
1364	    case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
1365		[#pending{tref=TRef,ref=Ref,caller=Caller}] ->
1366		    cancel_request_timer(Ref,TRef),
1367		    ct_gen_conn:return(Caller,E),
1368		    {noreply,State#state{pending=[]}};
1369		_ ->
1370		    ?error(ConnName,[{got_unexpected_msg,Other},
1371				       {expecting,Pending}]),
1372		    {noreply,State}
1373	    end
1374
1375    end.
1376
1377get_msg_id(Attrs) ->
1378    case lists:keyfind('message-id',1,Attrs) of
1379	{_,Str} ->
1380	    list_to_integer(Str);
1381	false ->
1382	    undefined
1383    end.
1384
1385decode_rpc_reply(MsgId,{_,Attrs,Content0}=E,#state{pending=Pending} = State) ->
1386    case lists:keytake(MsgId,#pending.msg_id,Pending) of
1387	{value, #pending{tref=TRef,ref=Ref,op=Op,caller=Caller}, Pending1} ->
1388	    cancel_request_timer(Ref,TRef),
1389	    Content = forward_xmlns_attr(Attrs,Content0),
1390	    {CallerReply,{ServerReply,State2}} =
1391		do_decode_rpc_reply(Op,Content,State#state{pending=Pending1}),
1392	    ct_gen_conn:return(Caller,CallerReply),
1393	    {ServerReply,State2};
1394	false ->
1395	    %% Result of send/2, when receiving a correct
1396	    %% rpc-reply. Can handle this only if there is just one
1397	    %% pending that matches (i.e. has undefined msg_id and op)
1398	    case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
1399		[#pending{tref=TRef,
1400			  ref=Ref,
1401			  msg_id=undefined,
1402			  op=undefined,
1403			  caller=Caller}] ->
1404		    cancel_request_timer(Ref,TRef),
1405		    ct_gen_conn:return(Caller,E),
1406		    {noreply,State#state{pending=[]}};
1407		_ ->
1408		    ConnName = (State#state.connection)#connection.name,
1409		    ?error(ConnName,[{got_unexpected_msg_id,MsgId},
1410						   {expecting,Pending}]),
1411		    {noreply,State}
1412	    end
1413    end.
1414
1415do_decode_rpc_reply(Op,Result,State)
1416  when Op==lock; Op==unlock; Op==edit_config; Op==delete_config;
1417   

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