1%%----------------------------------------------------------------------
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2012-2021. 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(), [option()]}.
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%% Functions supporting this are create_subscription/3
59%% get_event_streams/3.
60%%
61%%----------------------------------------------------------------------
62-module(ct_netconfc).
63
64-dialyzer(no_improper_lists).
65
66-include("ct_netconfc.hrl").
67-include("ct_util.hrl").
68-include_lib("xmerl/include/xmerl.hrl").
69
70%%----------------------------------------------------------------------
71%% External exports
72%%----------------------------------------------------------------------
73-export([connect/1,
74         connect/2,
75         disconnect/1,
76         session/1,
77         session/2,
78         session/3,
79         open/1,
80	 open/2,
81	 only_open/1,
82	 only_open/2,
83	 hello/1,
84	 hello/2,
85	 hello/3,
86	 close_session/1,
87	 close_session/2,
88	 kill_session/2,
89	 kill_session/3,
90	 send/2,
91	 send/3,
92	 send_rpc/2,
93	 send_rpc/3,
94	 lock/2,
95	 lock/3,
96	 unlock/2,
97	 unlock/3,
98	 get/2,
99	 get/3,
100	 get_config/3,
101	 get_config/4,
102	 edit_config/3,
103	 edit_config/4,
104	 edit_config/5,
105	 delete_config/2,
106	 delete_config/3,
107	 copy_config/3,
108	 copy_config/4,
109	 action/2,
110	 action/3,
111	 create_subscription/2,
112	 create_subscription/3,
113	 get_event_streams/1,
114	 get_event_streams/2,
115	 get_event_streams/3,
116	 get_capabilities/1,
117	 get_capabilities/2,
118	 get_session_id/1,
119	 get_session_id/2]).
120
121%% historic, no longer documented
122-export([create_subscription/1,
123	 create_subscription/4,
124	 create_subscription/5,
125	 create_subscription/6]).
126
127%%----------------------------------------------------------------------
128%% Exported types
129%%----------------------------------------------------------------------
130-export_type([client/0,
131              handle/0,
132              notification/0]).
133
134%%----------------------------------------------------------------------
135%% Internal exports
136%%----------------------------------------------------------------------
137%% ct_gen_conn callbacks
138-export([init/3,
139	 handle_msg/3,
140	 handle_msg/2,
141	 terminate/2,
142	 close/1]).
143
144%% ct_conn_log callback
145-export([format_data/2]).
146
147%%----------------------------------------------------------------------
148%% Internal defines
149%%----------------------------------------------------------------------
150-define(APPLICATION,?MODULE).
151-define(DEFAULT_STREAM,"NETCONF").
152
153-define(error(ConnName,Report),
154	error_logger:error_report([{ct_connection,ConnName},
155				   {client,self()},
156				   {module,?MODULE},
157				   {line,?LINE} |
158				   Report])).
159
160-define(is_timeout(T), (is_integer(T) orelse T==infinity)).
161-define(is_filter(F),
162	(?is_simple_xml(F)
163	 orelse (F==[])
164	 orelse (is_list(F) andalso ?is_simple_xml(hd(F))))).
165-define(is_simple_xml(Xml),
166	(is_atom(Xml) orelse (is_tuple(Xml) andalso is_atom(element(1,Xml))))).
167-define(is_string(S), (is_list(S) andalso is_integer(hd(S)))).
168
169%% Keys into the process dictionary.
170-define(KEY(T), {?MODULE, T}).
171
172%%----------------------------------------------------------------------
173%% Records
174%%----------------------------------------------------------------------
175%% Client state
176-record(state, {host,
177		port,
178		connection,      % #connection
179		capabilities,
180		session_id,
181		msg_id = 1,
182		hello_status,    % undefined | received | #pending{}
183                                 % string() | {error, Reason}
184		buf = false,     % binary() | list() | boolean()
185		pending = [],    % [#pending]
186		receivers = [] :: list() | pid()}).% notification destinations
187
188%% Run-time client options.
189-record(options, {ssh = [], % Options for the ssh application
190		  host,
191		  port = ?DEFAULT_PORT,
192		  timeout = ?DEFAULT_TIMEOUT,
193                  receivers = [],
194		  name,
195                  type}).
196
197%% Connection reference
198-record(connection, {reference, % {CM,Ch}
199		     host,
200		     port,
201		     name,
202                     type}).
203
204%% Pending replies from server
205-record(pending, {tref :: false | reference(),  % timer reference
206		  msg_id,
207                  op,
208		  caller}).% pid which sent the request
209
210%%----------------------------------------------------------------------
211%% Type declarations
212%%----------------------------------------------------------------------
213-type client() :: handle() | server_id() | ct:target_name().
214-opaque handle() :: pid().
215
216-type option() :: {host | ssh, host()}
217                | {port, inet:port_number()}
218                | {timeout, timeout()}
219                | {capability, string() | [string()]}
220                | {receiver, term()}
221                | ssh:client_option().
222
223-type session_option() :: {timeout,timeout()}
224                        | {receiver, term()}
225                        | {capability, string() | [string()]}.
226
227-type host() :: inet:hostname() | inet:ip_address().
228
229-type notification() :: {notification, xml_attributes(), [simple_xml()]}.
230
231-type stream_name() :: string().
232-type streams() :: [{stream_name(),[stream_data()]}].
233-type stream_data() :: {description,string()} |
234		       {replaySupport,string()} |
235		       {replayLogCreationTime,string()} |
236		       {replayLogAgedTime,string()}.
237%% See XML Schema for Event Notifications found in RFC5277 for further
238%% detail about the data format for the string values.
239
240-type error_reason() :: term().
241
242-type server_id() :: atom().
243
244-type simple_xml() :: {xml_tag(), xml_attributes(), xml_content()} |
245		      {xml_tag(), xml_content()} |
246		      xml_tag().
247-type xml_tag() :: atom().
248-type xml_attributes() :: [{xml_attribute_tag(),xml_attribute_value()}].
249-type xml_attribute_tag() :: atom().
250-type xml_attribute_value() :: string().
251-type xml_content() :: [simple_xml() | iolist()].
252-type xpath() :: {xpath,string()}.
253
254-type netconf_db() :: running | startup | candidate.
255-type xs_datetime() :: string().
256%% This date and time identifyer has the same format as the XML type
257%% dateTime and compliant to RFC3339. The format is
258%% "[-]CCYY-MM-DDThh:mm:ss[.s][Z|(+|-)hh:mm]"
259
260%%----------------------------------------------------------------------
261%% External interface functions
262%%----------------------------------------------------------------------
263
264%%----------------------------------------------------------------------
265%% Open an SSH connection to a Netconf server
266%% If the server options are specified in a configuration file, use
267%% open/2.
268
269%% connect/1
270
271-spec connect(Options) -> Result when
272      Options :: [option()],
273      Result :: {ok, handle()} | {error, error_reason()}.
274connect(Options) ->
275    connect(Options, #options{type = connection}, []).
276
277%% connect/2
278
279-spec connect(KeyOrName, ExtraOptions) -> Result when
280      KeyOrName :: ct:key_or_name(),
281      ExtraOptions :: [option()],
282      Result :: {ok, handle()} | {error, error_reason()}.
283
284connect(KeyOrName, ExtraOptions) ->
285    connect(make_opts(KeyOrName, ExtraOptions),
286            #options{name = KeyOrName, type = connection},
287            [{name, KeyOrName}]).
288
289%% connect/3
290
291connect(Opts, InitRec, NameOpt) ->
292    case make_options(Opts, InitRec) of
293        #options{} = Rec ->
294            start(Rec, NameOpt, false);
295        {error, _} = No ->
296            No
297    end.
298
299%% make_opts/2
300
301make_opts(KeyOrName, ExtraOptions) ->
302    SortedExtra = lists:keysort(1, ExtraOptions),
303    SortedConfig = lists:keysort(1, ct:get_config(KeyOrName, [])),
304    lists:ukeymerge(1, SortedConfig, SortedExtra).
305
306%%----------------------------------------------------------------------
307%% Close the given SSH connection.
308-spec disconnect(Conn) -> ok | {error,error_reason()} when
309      Conn :: handle().
310disconnect(Conn) ->
311    case call(Conn,get_ssh_connection) of
312        {ok,_} ->
313            ct_gen_conn:stop(Conn);
314        Error ->
315            Error
316    end.
317
318%%----------------------------------------------------------------------
319%% Open a netconf session as a channel on the given SSH connection,
320%% and exchange `hello' messages.
321
322%% session/1
323
324-spec session(Conn) -> Result when
325      Conn :: handle(),
326      Result :: {ok, handle()} | {error, error_reason()}.
327
328session(Conn) ->
329    session(Conn, [], #options{type = channel}, []).
330
331%% session/2
332
333-spec session(Conn, Options) -> Result when
334      Conn :: handle(),
335      Options :: [session_option()],
336      Result :: {ok, handle()} | {error, error_reason()};
337             (KeyOrName, Conn) -> Result when
338      KeyOrName :: ct:key_or_name(),
339      Conn :: handle(),
340      Result :: {ok, handle()} | {error, error_reason()}.
341
342session(Conn, Options) when is_list(Options) ->
343    session(Conn, Options, #options{type = channel}, []);
344
345session(KeyOrName, Conn) ->
346    session(Conn,
347            [],
348            #options{name = KeyOrName, type = channel},
349            [{name, KeyOrName}]).
350
351%% session/3
352
353-spec session(KeyOrName, Conn, Options) -> Result when
354      Conn :: handle(),
355      Options :: [session_option()],
356      KeyOrName :: ct:key_or_name(),
357      Result :: {ok, handle()} | {error, error_reason()}.
358
359session(KeyOrName, Conn, ExtraOptions) ->
360    session(Conn,
361            make_opts(KeyOrName, ExtraOptions),
362            #options{name = KeyOrName, type = channel},
363            [{name, KeyOrName}]).
364
365%% session/4
366
367session(Conn, Opts, InitRec, NameOpt) ->
368    T = make_ref(),
369    try
370        [_ | {ok, SshConn}] = [T | call(Conn, get_ssh_connection)],
371        [_ | #options{} = Rec] = [T | make_session_options(Opts, InitRec)],
372        [_ | {ok, Client} = Ok] = [T | start(SshConn, Rec, NameOpt, true)],
373        [_ | ok] = [T | hello(Client, caps(Opts), Rec#options.timeout)],
374        Ok
375    catch
376        error: {badmatch, [T | Error]} ->
377            Error
378    end.
379
380%% caps/1
381
382caps(Opts) ->
383    [T || {capability, _} = T <- Opts].
384
385%%----------------------------------------------------------------------
386%% Open a netconf session and exchange 'hello' messages.
387%% If the server options are specified in a configuration file, use
388%% open/2.
389
390%% open/1
391
392-spec open(Options) -> Result when
393      Options :: [option()],
394      Result :: {ok, handle()} | {error, error_reason()}.
395
396open(Options) ->
397    open(Options,
398         #options{type = connection_and_channel},
399         [],
400         true).
401
402-spec open(KeyOrName, ExtraOption) -> Result when
403      KeyOrName :: ct:key_or_name(),
404      ExtraOption :: [option()],
405      Result :: {ok, handle()} | {error, error_reason()}.
406
407open(KeyOrName, ExtraOpts) ->
408    open(KeyOrName, ExtraOpts, true).
409
410%% open/3
411
412open(KeyOrName, ExtraOptions, Hello) ->
413    open(make_opts(KeyOrName, ExtraOptions),
414         #options{name = KeyOrName, type = connection_and_channel},
415         [{name, KeyOrName}],
416         Hello).
417
418%% open/4
419
420open(Opts, InitRec, NameOpt, Hello) ->
421    T = make_ref(),
422    try
423        [_, #options{} = Rec] = [T, make_options(Opts, InitRec)],
424        [_, {ok, Client} = Ok | true] = [T, start(Rec, NameOpt, true) | Hello],
425        [_, ok] = [T, hello(Client, caps(Opts), Rec#options.timeout)],
426        Ok
427    catch
428        error: {badmatch, [T, Res | _]} ->
429            Res
430    end.
431
432%% start/3
433
434start(#options{host = undefined}, _, _) ->
435    {error, no_host_address};
436
437start(#options{port = undefined}, _, _) ->
438    {error, no_port};
439
440start(#options{host = Host, port = Port} = Opts, NameOpt, Fwd) ->
441    start({Host, Port}, Opts, NameOpt, Fwd).
442
443%% start/4
444
445start(Ep, Opts, NameOpt, Fwd) ->
446    ct_gen_conn:start(Ep, Opts, ?MODULE, [{reconnect, false},
447                                          {use_existing_connection, false},
448                                          {forward_messages, Fwd}
449                                          | NameOpt]).
450
451%%----------------------------------------------------------------------
452%% Like open/1,2, but no 'hello' message is sent.
453
454-spec only_open(Options) -> Result when
455      Options :: [option()],
456      Result :: {ok, handle()} | {error, error_reason()}.
457
458only_open(Options) ->
459    open(Options, #options{type = connection_and_channel}, [], false).
460
461-spec only_open(KeyOrName, ExtraOptions) -> Result when
462      KeyOrName :: ct:key_or_name(),
463      ExtraOptions :: [option()],
464      Result :: {ok, handle()} | {error, error_reason()}.
465
466only_open(KeyOrName, ExtraOpts) ->
467    open(KeyOrName, ExtraOpts, false).
468
469%%----------------------------------------------------------------------
470%% Send a 'hello' message.
471
472%% hello/1
473
474-spec hello(Client) -> Result when
475      Client :: handle(),
476      Result :: ok | {error, error_reason()}.
477
478hello(Client) ->
479    hello(Client, [], ?DEFAULT_TIMEOUT).
480
481%% hello/2
482
483-spec hello(Client, Timeout) -> Result when
484      Client :: handle(),
485      Timeout :: timeout(),
486      Result :: ok | {error, error_reason()}.
487
488hello(Client, Timeout) ->
489    hello(Client, [], Timeout).
490
491%% hello/3
492
493-spec hello(Client, Options, Timeout) -> Result when
494      Client :: handle(),
495      Options :: [{capability, [string()]}],
496      Timeout :: timeout(),
497      Result :: ok | {error, error_reason()}.
498
499hello(Client, Options, Timeout) ->
500    call(Client, {hello, Options, Timeout}).
501
502
503%%----------------------------------------------------------------------
504%% Get the session id for the session specified by Client.
505-spec get_session_id(Client) -> Result when
506      Client :: client(),
507      Result :: pos_integer() | {error,error_reason()}.
508get_session_id(Client) ->
509    get_session_id(Client, ?DEFAULT_TIMEOUT).
510
511-spec get_session_id(Client, Timeout) -> Result when
512      Client :: client(),
513      Timeout :: timeout(),
514      Result :: pos_integer() | {error,error_reason()}.
515get_session_id(Client, Timeout) ->
516    call(Client, get_session_id, Timeout).
517
518%%----------------------------------------------------------------------
519%% Get the server side capabilities.
520-spec get_capabilities(Client) -> Result when
521      Client :: client(),
522      Result :: [string()] | {error,error_reason()}.
523get_capabilities(Client) ->
524    get_capabilities(Client, ?DEFAULT_TIMEOUT).
525
526-spec get_capabilities(Client, Timeout) -> Result when
527      Client :: client(),
528      Timeout :: timeout(),
529      Result :: [string()] | {error,error_reason()}.
530get_capabilities(Client, Timeout) ->
531    call(Client, get_capabilities, Timeout).
532
533%%----------------------------------------------------------------------
534%% Send an XML document to the server.
535-spec send(Client, SimpleXml) -> Result when
536      Client :: client(),
537      SimpleXml :: simple_xml(),
538      Result :: simple_xml() | {error,error_reason()}.
539send(Client, SimpleXml) ->
540    send(Client, SimpleXml, ?DEFAULT_TIMEOUT).
541
542-spec send(Client, SimpleXml, Timeout) -> Result when
543      Client :: client(),
544      SimpleXml :: simple_xml(),
545      Timeout :: timeout(),
546      Result :: simple_xml() | {error,error_reason()}.
547send(Client, SimpleXml, Timeout) ->
548    call(Client,{send, Timeout, SimpleXml}).
549
550%%----------------------------------------------------------------------
551%% Wrap the given XML document in a valid netconf 'rpc' request and
552%% send to the server.
553-spec send_rpc(Client, SimpleXml) -> Result when
554      Client :: client(),
555      SimpleXml :: simple_xml(),
556      Result :: [simple_xml()] | {error,error_reason()}.
557send_rpc(Client, SimpleXml) ->
558    send_rpc(Client, SimpleXml, ?DEFAULT_TIMEOUT).
559
560-spec send_rpc(Client, SimpleXml, Timeout) -> Result when
561      Client :: client(),
562      SimpleXml :: simple_xml(),
563      Timeout :: timeout(),
564      Result :: [simple_xml()] | {error,error_reason()}.
565send_rpc(Client, SimpleXml, Timeout) ->
566    call(Client,{send_rpc, SimpleXml, Timeout}).
567
568%%----------------------------------------------------------------------
569%% Send a 'lock' request.
570-spec lock(Client, Target) -> Result when
571      Client :: client(),
572      Target :: netconf_db(),
573      Result :: ok | {error,error_reason()}.
574lock(Client, Target) ->
575    lock(Client, Target,?DEFAULT_TIMEOUT).
576
577-spec lock(Client, Target, Timeout) -> Result when
578      Client :: client(),
579      Target :: netconf_db(),
580      Timeout :: timeout(),
581      Result :: ok | {error,error_reason()}.
582lock(Client, Target, Timeout) ->
583    call(Client,{send_rpc_op,lock,[Target],Timeout}).
584
585%%----------------------------------------------------------------------
586%% Send a 'unlock' request.
587-spec unlock(Client, Target) -> Result when
588      Client :: client(),
589      Target :: netconf_db(),
590      Result :: ok | {error,error_reason()}.
591unlock(Client, Target) ->
592    unlock(Client, Target,?DEFAULT_TIMEOUT).
593
594-spec unlock(Client, Target, Timeout) -> Result when
595      Client :: client(),
596      Target :: netconf_db(),
597      Timeout :: timeout(),
598      Result :: ok | {error,error_reason()}.
599unlock(Client, Target, Timeout) ->
600    call(Client, {send_rpc_op, unlock, [Target], Timeout}).
601
602%%----------------------------------------------------------------------
603%% Send a 'get' request.
604-spec get(Client, Filter) -> Result when
605      Client :: client(),
606      Filter :: simple_xml() | xpath(),
607      Result :: {ok,[simple_xml()]} | {error,error_reason()}.
608get(Client, Filter) ->
609    get(Client, Filter, ?DEFAULT_TIMEOUT).
610
611-spec get(Client, Filter, Timeout) -> Result when
612      Client :: client(),
613      Filter :: simple_xml() | xpath(),
614      Timeout :: timeout(),
615      Result :: {ok,[simple_xml()]} | {error,error_reason()}.
616get(Client, Filter, Timeout) ->
617    call(Client,{send_rpc_op, get, [Filter], Timeout}).
618
619%%----------------------------------------------------------------------
620%% Send a 'get-config' request.
621-spec get_config(Client, Source, Filter) -> Result when
622      Client :: client(),
623      Source :: netconf_db(),
624      Filter :: simple_xml() | xpath(),
625      Result :: {ok,[simple_xml()]} | {error,error_reason()}.
626get_config(Client, Source, Filter) ->
627    get_config(Client, Source, Filter, ?DEFAULT_TIMEOUT).
628
629-spec get_config(Client, Source, Filter, Timeout) -> Result when
630      Client :: client(),
631      Source :: netconf_db(),
632      Filter :: simple_xml() | xpath(),
633      Timeout :: timeout(),
634      Result :: {ok,[simple_xml()]} | {error,error_reason()}.
635get_config(Client, Source, Filter, Timeout) ->
636    call(Client, {send_rpc_op, get_config, [Source, Filter], Timeout}).
637
638%%----------------------------------------------------------------------
639%% Send a 'edit-config' request.
640-spec edit_config(Client, Target, Config) -> Result when
641      Client :: client(),
642      Target :: netconf_db(),
643      Config :: simple_xml() | [simple_xml()],
644      Result :: ok | {error,error_reason()}.
645edit_config(Client, Target, Config) ->
646    edit_config(Client, Target, Config, ?DEFAULT_TIMEOUT).
647
648-spec edit_config(Client, Target, Config, OptParams) -> Result when
649      Client :: client(),
650      Target :: netconf_db(),
651      Config :: simple_xml() | [simple_xml()],
652      OptParams :: [simple_xml()],
653      Result :: ok | {error,error_reason()};
654                 (Client, Target, Config, Timeout) -> Result when
655      Client :: client(),
656      Target :: netconf_db(),
657      Config :: simple_xml(),
658      Timeout :: timeout(),
659      Result :: ok | {error,error_reason()}.
660edit_config(Client, Target, Config, Timeout) when ?is_timeout(Timeout) ->
661    edit_config(Client, Target, Config, [], Timeout);
662edit_config(Client, Target, Config, OptParams) when is_list(OptParams) ->
663    edit_config(Client, Target, Config, OptParams, ?DEFAULT_TIMEOUT).
664
665-spec edit_config(Client, Target, Config, OptParams, Timeout) -> Result when
666      Client :: client(),
667      Target :: netconf_db(),
668      Config :: simple_xml() | [simple_xml()],
669      OptParams :: [simple_xml()],
670      Timeout :: timeout(),
671      Result :: ok | {error,error_reason()}.
672edit_config(Client, Target, Config, OptParams, Timeout) when not is_list(Config)->
673    edit_config(Client, Target, [Config], OptParams, Timeout);
674edit_config(Client, Target, Config, OptParams, Timeout) ->
675    call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
676
677
678%%----------------------------------------------------------------------
679%% Send a 'delete-config' request.
680-spec delete_config(Client, Target) -> Result when
681      Client :: client(),
682      Target :: startup | candidate,
683      Result :: ok | {error,error_reason()}.
684delete_config(Client, Target) ->
685    delete_config(Client, Target, ?DEFAULT_TIMEOUT).
686
687-spec delete_config(Client, Target, Timeout) -> Result when
688      Client :: client(),
689      Target :: startup | candidate,
690      Timeout :: timeout(),
691      Result :: ok | {error,error_reason()}.
692delete_config(Client, Target, Timeout) when Target == startup;
693					    Target == candidate ->
694    call(Client,{send_rpc_op, delete_config, [Target], Timeout}).
695
696%%----------------------------------------------------------------------
697%% Send a 'copy-config' request.
698-spec copy_config(Client, Target, Source) -> Result when
699      Client :: client(),
700      Target :: netconf_db(),
701      Source :: netconf_db(),
702      Result :: ok | {error,error_reason()}.
703copy_config(Client, Source, Target) ->
704    copy_config(Client, Source, Target, ?DEFAULT_TIMEOUT).
705
706-spec copy_config(Client, Target, Source, Timeout) -> Result when
707      Client :: client(),
708      Target :: netconf_db(),
709      Source :: netconf_db(),
710      Timeout :: timeout(),
711      Result :: ok | {error,error_reason()}.
712copy_config(Client, Target, Source, Timeout) ->
713    call(Client,{send_rpc_op, copy_config, [Target, Source], Timeout}).
714
715%%----------------------------------------------------------------------
716%% Execute an action.
717-spec action(Client, Action) -> Result when
718      Client :: client(),
719      Action :: simple_xml(),
720      Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
721action(Client,Action) ->
722    action(Client,Action,?DEFAULT_TIMEOUT).
723
724-spec action(Client, Action, Timeout) -> Result when
725      Client :: client(),
726      Action :: simple_xml(),
727      Timeout :: timeout(),
728      Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
729action(Client,Action,Timeout) ->
730    call(Client,{send_rpc_op, action, [Action], Timeout}).
731
732%%----------------------------------------------------------------------
733%% Send a 'create-subscription' request
734%% See RFC5277, NETCONF Event Notifications
735
736%% create_subscription/2
737
738-spec create_subscription(Client, Values) -> Result when
739      Client :: client(),
740      Values :: #{stream => Stream,
741                  filter => Filter,
742                  start => StartTime,
743                  stop => StopTime},
744      Stream :: stream_name(),
745      Filter :: simple_xml() | [simple_xml()],
746      StartTime :: xs_datetime(),
747      StopTime :: xs_datetime(),
748      Result :: ok | {error,error_reason()};
749      %% historic, no longer documented
750                         (Client, list() | timeout()) -> Result when
751      Client :: client(),
752      Result :: ok | {error,error_reason()}.
753
754create_subscription(Client, #{} = Values) ->
755    create_subscription(Client, Values, ?DEFAULT_TIMEOUT);
756
757%% historic clauses
758create_subscription(Client, Timeout)
759  when ?is_timeout(Timeout) ->
760    create_subscription(Client, #{}, Timeout);
761create_subscription(Client, Stream)
762  when ?is_string(Stream) ->
763    create_subscription(Client, #{stream => Stream});
764create_subscription(Client, Filter)
765  when ?is_filter(Filter) ->
766    create_subscription(Client, #{filter => Filter}).
767
768-spec create_subscription(Client, Values, Timeout) -> Result when
769      Client :: client(),
770      Values :: #{stream => Stream,
771                  filter => Filter,
772                  start => StartTime,
773                  stop => StopTime},
774      Stream :: stream_name(),
775      Filter :: simple_xml() | [simple_xml()],
776      StartTime :: xs_datetime(),
777      StopTime :: xs_datetime(),
778      Timeout :: timeout(),
779      Result :: ok | {error,error_reason()};
780      %% historic, no longer documented
781                         (Client, list(), list() | timeout()) -> Result when
782      Client :: client(),
783      Result :: ok | {error,error_reason()}.
784
785create_subscription(Client, #{} = Values, Timeout) ->
786    Keys = [{stream, ?DEFAULT_STREAM},
787            {filter, undefined},
788            {start, undefined},
789            {stop, undefined}],
790    call(Client, {send_rpc_op, {create_subscription, self()},
791                               [maps:get(K, Values, D) || {K,D} <- Keys],
792                               Timeout});
793
794%% historic clauses, arity 3
795create_subscription(Client, Stream, Timeout)
796  when ?is_string(Stream), ?is_timeout(Timeout) ->
797    create_subscription(Client, #{stream => Stream}, Timeout);
798create_subscription(Client, StartTime, StopTime)
799  when ?is_string(StartTime), ?is_string(StopTime) ->
800    create_subscription(Client, #{start => StartTime, stop => StopTime});
801create_subscription(Client, Filter, Timeout)
802  when ?is_filter(Filter), ?is_timeout(Timeout) ->
803    create_subscription(Client, #{filter => Filter}, Timeout);
804create_subscription(Client, Stream, Filter)
805  when ?is_string(Stream), ?is_filter(Filter) ->
806    create_subscription(Client, #{stream => Stream, filter => Filter}).
807
808%% historic clauses, arity 1,4-5
809create_subscription(Client) ->
810    create_subscription(Client, #{}).
811create_subscription(Client, StartTime, StopTime, Timeout)
812  when ?is_string(StartTime), ?is_string(StopTime), ?is_timeout(Timeout) ->
813    Values = #{start => StartTime,
814               stop => StopTime},
815    create_subscription(Client, Values, Timeout);
816create_subscription(Client, Stream, StartTime, StopTime)
817  when ?is_string(Stream), ?is_string(StartTime), ?is_string(StopTime) ->
818    create_subscription(Client, #{stream => Stream,
819                                  start => StartTime,
820                                  stop => StopTime});
821create_subscription(Client, Filter, StartTime, StopTime)
822  when ?is_filter(Filter), ?is_string(StartTime), ?is_string(StopTime) ->
823    create_subscription(Client, #{filter => Filter,
824                                  start => StartTime,
825                                  stop => StopTime});
826create_subscription(Client, Stream, Filter, Timeout)
827  when ?is_string(Stream), ?is_filter(Filter), ?is_timeout(Timeout) ->
828    Values = #{stream => Stream,
829               filter => Filter},
830    create_subscription(Client, Values, Timeout).
831create_subscription(Client, Stream, StartTime, StopTime, Timeout)
832  when ?is_string(Stream), ?is_string(StartTime), ?is_string(StopTime),
833       ?is_timeout(Timeout) ->
834    Values = #{stream => Stream,
835               start => StartTime,
836               stop => StopTime},
837    create_subscription(Client, Values, Timeout);
838create_subscription(Client, Stream, Filter, StartTime, StopTime)
839  when ?is_string(Stream), ?is_filter(Filter), ?is_string(StartTime),
840       ?is_string(StopTime) ->
841    create_subscription(Client, #{stream => Stream,
842                                  filter => Filter,
843                                  start => StartTime,
844                                  stop => StopTime}).
845create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout) ->
846    Values = #{stream => Stream,
847               filter => Filter,
848               start => StartTime,
849               stop => StopTime},
850    create_subscription(Client, Values, Timeout).
851
852%%----------------------------------------------------------------------
853%% Send a request to get the given event streams
854%% See RFC5277, NETCONF Event Notifications
855-spec get_event_streams(Client)
856		       -> Result when
857      Client :: client(),
858      Result :: {ok,streams()} | {error,error_reason()}.
859get_event_streams(Client) ->
860    get_event_streams(Client,[],?DEFAULT_TIMEOUT).
861
862-spec get_event_streams(Client, Timeout)
863		       -> Result when
864      Client :: client(),
865      Timeout :: timeout(),
866      Result :: {ok,streams()} | {error,error_reason()};
867                       (Client, Streams) -> Result when
868      Client :: client(),
869      Streams :: [stream_name()],
870      Result :: {ok,streams()} | {error,error_reason()}.
871get_event_streams(Client,Timeout) when is_integer(Timeout); Timeout==infinity ->
872    get_event_streams(Client,[],Timeout);
873get_event_streams(Client,Streams) when is_list(Streams) ->
874    get_event_streams(Client,Streams,?DEFAULT_TIMEOUT).
875
876-spec get_event_streams(Client, Streams, Timeout)
877		       -> Result when
878      Client :: client(),
879      Streams :: [stream_name()],
880      Timeout :: timeout(),
881      Result :: {ok,streams()} | {error,error_reason()}.
882get_event_streams(Client,Streams,Timeout) ->
883    call(Client,{get_event_streams,Streams,Timeout}).
884
885
886%%----------------------------------------------------------------------
887%% Send a 'close-session' request
888-spec close_session(Client) -> Result when
889      Client :: client(),
890      Result :: ok | {error,error_reason()}.
891close_session(Client) ->
892    close_session(Client, ?DEFAULT_TIMEOUT).
893
894-spec close_session(Client, Timeout) -> Result when
895      Client :: client(),
896      Timeout :: timeout(),
897      Result :: ok | {error,error_reason()}.
898close_session(Client, Timeout) ->
899    call(Client,{send_rpc_op, close_session, [], Timeout}, true).
900
901
902%%----------------------------------------------------------------------
903%% Send a 'kill-session' request
904-spec kill_session(Client, SessionId) -> Result when
905      Client :: client(),
906      SessionId :: pos_integer(),
907      Result :: ok | {error,error_reason()}.
908kill_session(Client, SessionId) ->
909    kill_session(Client, SessionId, ?DEFAULT_TIMEOUT).
910
911-spec kill_session(Client, SessionId, Timeout) -> Result when
912      Client :: client(),
913      SessionId :: pos_integer(),
914      Timeout :: timeout(),
915      Result :: ok | {error,error_reason()}.
916kill_session(Client, SessionId, Timeout) ->
917    call(Client,{send_rpc_op, kill_session, [SessionId], Timeout}).
918
919
920%%----------------------------------------------------------------------
921%% Callback functions
922%%----------------------------------------------------------------------
923
924%% init/3
925
926init(_KeyOrName,{CM,{Host,Port}},Options) ->
927    case ssh_channel(#connection{reference=CM,host=Host,port=Port},Options) of
928        {ok,Connection} ->
929	    {ok, CM, #state{connection = Connection,
930                            receivers = Options#options.receivers}};
931	{error,Reason}->
932	    {error,Reason}
933    end;
934init(_KeyOrName,{_Host,_Port},Options) when Options#options.type==connection ->
935    case ssh_connect(Options) of
936        {ok, Connection} ->
937	    ConnPid = Connection#connection.reference,
938            {ok, ConnPid, #state{connection = Connection}};
939        Error ->
940            Error
941    end;
942init(_KeyOrName,{_Host,_Port},Options) ->
943    case ssh_open(Options) of
944	{ok, Connection} ->
945	    {ConnPid,_} = Connection#connection.reference,
946	    {ok, ConnPid, #state{connection = Connection,
947                                 receivers = Options#options.receivers}};
948	{error,Reason}->
949	    {error,Reason}
950    end.
951
952%% terminate/2
953
954terminate(_, #state{connection=Connection}) ->
955    ssh_close(Connection),
956    ok.
957
958%% handle_msg/3
959
960%% Send hello and return to the caller only after reception of the
961%% server's hello.
962handle_msg({hello, Options, Timeout},
963           From,
964	   #state{connection = Connection,
965                  hello_status = HelloStatus}
966           = State) ->
967    case do_send(Connection, client_hello(Options)) of
968	ok when HelloStatus == undefined -> %% server hello not yet received
969            TRef = set_request_timer(Timeout, hello),
970            {noreply, State#state{hello_status = #pending{tref = TRef,
971                                                          caller = From}}};
972        ok ->                               %% or yes: negotiate version
973            handle_capx(State);
974        Error ->
975	    {stop, Error, State}
976    end;
977
978handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
979    Reply =
980        case Connection#connection.reference of
981            {_,_} -> {error,not_an_ssh_connection};
982            CM -> {ok,{CM,{Connection#connection.host,
983                           Connection#connection.port}}}
984        end,
985    {reply, Reply, State};
986
987%% Request before server hello. Possible with only_open, since a
988%% handle is then returned without waiting for the server.
989handle_msg(_, _From, #state{session_id = undefined} = State) ->
990    {reply, {error, waiting_for_hello}, State};
991
992handle_msg(get_capabilities, _From, #state{capabilities = Caps} = State) ->
993    {reply, Caps, State};
994
995handle_msg(get_session_id, _From, #state{session_id = Id} = State) ->
996    {reply, Id, State};
997
998handle_msg({send, Timeout, SimpleXml},
999           From,
1000           #state{connection = Connection,
1001                  pending = Pending}
1002           = State) ->
1003    case do_send(Connection, SimpleXml) of
1004        ok ->
1005            TRef = set_request_timer(Timeout, send),
1006            {noreply, State#state{pending = [#pending{tref = TRef,
1007                                                      caller = From}
1008                                             | Pending]}};
1009        Error ->
1010            {reply, Error, State}
1011    end;
1012
1013handle_msg({send_rpc, SimpleXml, Timeout}, From, State) ->
1014    do_send_rpc(undefined, SimpleXml, Timeout, From, State);
1015
1016handle_msg({send_rpc_op, Op, Data, Timeout}, From, State) ->
1017    SimpleXml = encode_rpc_operation(Op,Data),
1018    do_send_rpc(Op, SimpleXml, Timeout, From, State);
1019
1020handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
1021    Filter = {netconf,?NETMOD_NOTIF_NAMESPACE_ATTR,
1022             [{streams,[{stream,[{name,[Name]}]} || Name <- Streams]}]},
1023    SimpleXml = encode_rpc_operation(get,[Filter]),
1024    do_send_rpc(Op, SimpleXml, Timeout, From, State).
1025
1026handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
1027    ssh_connection:adjust_window(CM,Ch,size(Data)),
1028    log(State#state.connection, recv, Data),
1029    handle_data(Data, State);
1030
1031handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
1032    %% _SshCloseMsg can probably be one of
1033    %% {eof,Ch}
1034    %% {exit_status,Ch,Status}
1035    %% {exit_signal,Ch,ExitSignal,ErrorMsg,LanguageString}
1036    %% {signal,Ch,Signal}
1037
1038    %% This might e.g. happen if the server terminates the connection,
1039    %% as in kill-session (or if ssh:close is called from somewhere
1040    %% unexpected).
1041
1042    %%! Log this??
1043    %%! Currently the log will say that the client closed the
1044    %%! connection - due to terminate/2
1045
1046    {stop, State};
1047
1048handle_msg({timeout, TRef, hello},
1049           #state{hello_status = #pending{tref = TRef,
1050                                          caller = From}}
1051           = State) ->
1052    ct_gen_conn:return(From, {error, {hello_session_failed, timeout}}),
1053    {stop, State#state{hello_status = {error,timeout}}};
1054
1055handle_msg({timeout, TRef, Op}, #state{pending = Pending} = State) ->
1056    case lists:keytake(TRef, #pending.tref, Pending) of
1057        {value, #pending{caller = From}, Rest} ->
1058            ct_gen_conn:return(From, {error, timeout}),
1059            %% Discard received bytes in hope that the server has sent
1060            %% an incomplete message. Otherwise this is doomed to
1061            %% leave the connection in an unusable state.
1062            {if Op == close_session -> stop; true -> noreply end,
1063             State#state{pending = Rest,
1064                         buf = is_binary(State#state.buf)}};
1065        false ->
1066            {noreply, State}
1067    end.
1068
1069%% close/1
1070
1071%% Called by ct_util_server to close registered connections before terminate.
1072close(Client) ->
1073    case get_handle(Client) of
1074	{ok,Pid} ->
1075	    case ct_gen_conn:stop(Pid) of
1076		{error,{process_down,Pid,noproc}} ->
1077		    {error,already_closed};
1078		Result ->
1079		    Result
1080	    end;
1081	Error ->
1082	    Error
1083    end.
1084
1085
1086%%----------------------------------------------------------------------
1087%% Internal functions
1088%%----------------------------------------------------------------------
1089call(Client, Msg) ->
1090    call(Client, Msg, infinity, false).
1091call(Client, Msg, Timeout) when is_integer(Timeout); Timeout==infinity ->
1092    call(Client, Msg, Timeout, false);
1093call(Client, Msg, WaitStop) when is_boolean(WaitStop) ->
1094    call(Client, Msg, infinity, WaitStop).
1095call(Client, Msg, Timeout, WaitStop) ->
1096    case get_handle(Client) of
1097	{ok,Pid} ->
1098	    case ct_gen_conn:call(Pid,Msg,Timeout) of
1099		{error,{process_down,Pid,noproc}} ->
1100		    {error,no_such_client};
1101		{error,{process_down,Pid,normal}} when WaitStop ->
1102		    %% This will happen when server closes connection
1103		    %% before client received rpc-reply on
1104		    %% close-session.
1105		    ok;
1106		{error,{process_down,Pid,normal}} ->
1107		    {error,closed};
1108		{error,{process_down,Pid,Reason}} ->
1109		    {error,{closed,Reason}};
1110		Other when WaitStop ->
1111		    MRef = erlang:monitor(process,Pid),
1112		    receive
1113			{'DOWN',MRef,process,Pid,Normal} when Normal==normal;
1114							      Normal==noproc ->
1115			    Other;
1116			{'DOWN',MRef,process,Pid,Reason} ->
1117			    {error,{{closed,Reason},Other}}
1118		    after Timeout ->
1119			    erlang:demonitor(MRef, [flush]),
1120			    {error,{timeout,Other}}
1121		    end;
1122		Other ->
1123		    Other
1124	    end;
1125	Error ->
1126	    Error
1127    end.
1128
1129get_handle(Client) when is_pid(Client) ->
1130    {ok,Client};
1131get_handle(Client) ->
1132    case ct_util:get_connection(Client, ?MODULE) of
1133	{ok,{Pid,_}} ->
1134	    {ok,Pid};
1135	{error,no_registered_connection} ->
1136	    {error,{no_connection_found,Client}};
1137	Error ->
1138	    Error
1139    end.
1140
1141%% make_options/2
1142
1143make_options(Opts, Rec) ->
1144    make_options(Opts, Rec#options{port = undefined}, fun opt/2).
1145
1146opt({T, Host}, Rec)
1147  when T == ssh;
1148       T == host ->
1149    Rec#options{host = Host};
1150
1151opt({port, Port}, Rec) ->
1152    Rec#options{port = Port};
1153
1154opt({timeout, Tmo}, Rec)
1155  when is_integer(Tmo);
1156       Tmo == infinity ->
1157    Rec#options{timeout = Tmo};
1158
1159opt({timeout, _} = T, _) ->
1160    throw(T);
1161
1162opt({capability, _}, Rec) ->
1163    Rec;
1164
1165opt({receiver, Dest}, #options{receivers = T} = Rec) ->
1166    Rec#options{receivers = [Dest | T]};
1167
1168opt(Opt, #options{ssh = Opts} = Rec) -> %% option verified by ssh
1169    Rec#options{ssh = [Opt | Opts]}.
1170
1171%% make_session_options/2
1172
1173make_session_options(Opts, Rec) ->
1174    make_options(Opts, Rec, fun session_opt/2).
1175
1176session_opt({receiver, Dest}, #options{receivers = T} = Rec) ->
1177    Rec#options{receivers = [Dest | T]};
1178
1179session_opt({capability, _}, Rec) ->
1180    Rec;
1181
1182session_opt({timeout, Tmo}, Rec)
1183  when is_integer(Tmo);
1184       Tmo == infinity ->
1185    Rec#options{timeout = Tmo};
1186
1187session_opt(T, _Rec) ->
1188    throw(T).
1189
1190%% make_options/3
1191
1192make_options(Opts, Rec, F) ->
1193    try
1194        #options{} = lists:foldl(F, Rec, Opts)
1195    catch
1196        T ->
1197            {error, {invalid_option, T}}
1198    end.
1199
1200%%%-----------------------------------------------------------------
1201
1202set_request_timer(infinity, _) ->
1203    false;
1204
1205set_request_timer(Tmo, Op) ->
1206    erlang:start_timer(Tmo, self(), Op).
1207
1208%%%-----------------------------------------------------------------
1209
1210cancel_request_timer(false) ->
1211    ok;
1212
1213cancel_request_timer(TRef) ->
1214    erlang:cancel_timer(TRef).
1215
1216%%%-----------------------------------------------------------------
1217
1218%% client_hello/1
1219%%
1220%% Prepend the 1.0 base capability only if none is specified by the
1221%% user. Store the versions in the process dictionary until they're
1222%% examined upon reception of server capabilities in handle_capx/1.
1223
1224client_hello(Opts)
1225  when is_list(Opts) ->
1226    UserCaps = [{T, cap(lists:flatten(Cs))} || {capability = T, Cs} <- Opts],
1227    Vsns = versions(UserCaps),
1228    put(?KEY(protocol_vsn), Vsns),
1229    {hello,
1230     ?NETCONF_NAMESPACE_ATTR,
1231     [{capabilities, [{capability, [?NETCONF_BASE_CAP, ?NETCONF_BASE_CAP_VSN]}
1232                      || [] == Vsns]
1233                     ++ UserCaps}]}.
1234
1235%% cap/1
1236%%
1237%% Let NETCONF capabilities be specified in the shorthand documented in
1238%% RFC 6241.
1239
1240%% This shorthand is documented in RFC 6241 10.4 NETCONF Capabilities
1241%% URNS, but not in 8 Capabilities.
1242cap(":base:" ++ _ = Str) ->
1243    ["urn:ietf:params:netconf", Str];
1244
1245cap([$:|_] = Str) ->
1246    ["urn:ietf:params:netconf:capability", Str];
1247
1248cap(Str) ->
1249    [Str].
1250
1251%% versions/1
1252%%
1253%% Extract base protocol versions from capability options.
1254
1255versions(Opts) ->
1256    [V || {capability, L} <- Opts,
1257          S <- L,
1258          ?NETCONF_BASE_CAP ++ X <- [lists:flatten(S)],
1259          V <- [lists:takewhile(fun(C) -> C /= $? end, X)]].
1260
1261%% handle_capx/1
1262%%
1263%% Ignore parameters as RFC 6241 (NETCONF 1.1) requires in 8.1
1264%% Capabilities Exchange. Be overly lenient with whitespace since RFC
1265%% 6241 gives examples with significant trailing whitespace.
1266
1267handle_capx(#state{hello_status = received, capabilities = Caps} = S) ->
1268    Remote = [V || ?NETCONF_BASE_CAP ++ X <- Caps,
1269                   [V|_] <- [string:lexemes(X, "? \t\r\n")]],
1270    Local = erase(?KEY(protocol_vsn)),
1271    case protocol_vsn(Local, Remote) of
1272        false when Remote == [] ->
1273            Reason = {incorrect_hello, no_base_capability_found},
1274            {stop, {error, Reason}, S};
1275        false ->
1276            Reason = {incompatible_base_capability_vsn, lists:min(Remote)},
1277            {stop, {error, Reason}, S};
1278        Vsn ->
1279            put(?KEY(chunk), Vsn /= "1.0"),
1280            {reply, ok, rebuf(Vsn, S#state{hello_status = Vsn})}
1281    end;
1282
1283handle_capx(#state{hello_status = {error, _} = No} = S) ->
1284    {stop, No, S}.
1285
1286%% rebuf/2
1287%%
1288%% Turn the message buffer into a list for 1.1 chunking if the
1289%% negotiated protocol version is > 1.0.
1290
1291rebuf("1.0", S) ->
1292    S;
1293
1294rebuf(_, #state{buf = Bin} = S) ->
1295    S#state{buf = [Bin, 3]}.
1296
1297%% protocol_vsn/2
1298
1299protocol_vsn([], Vsns) ->
1300    protocol_vsn(["1.0"], Vsns);
1301
1302protocol_vsn(Local, Remote) ->
1303    lists:max([false | [V || V <- Remote, lists:member(V, Local)]]).
1304
1305%%%-----------------------------------------------------------------
1306
1307encode_rpc_operation(Lock,[Target]) when Lock==lock; Lock==unlock ->
1308    {Lock,[{target,[Target]}]};
1309encode_rpc_operation(get,[Filter]) ->
1310    {get,filter(Filter)};
1311encode_rpc_operation(get_config,[Source,Filter]) ->
1312    {'get-config',[{source,[Source]}] ++ filter(Filter)};
1313encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
1314    {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,Config}]};
1315encode_rpc_operation(delete_config,[Target]) ->
1316    {'delete-config',[{target,[Target]}]};
1317encode_rpc_operation(copy_config,[Target,Source]) ->
1318    {'copy-config',[{target,[Target]},{source,[Source]}]};
1319encode_rpc_operation(action,[Action]) ->
1320    {action,?ACTION_NAMESPACE_ATTR,[{data,[Action]}]};
1321encode_rpc_operation(kill_session,[SessionId]) ->
1322    {'kill-session',[{'session-id',[integer_to_list(SessionId)]}]};
1323encode_rpc_operation(close_session,[]) ->
1324    'close-session';
1325encode_rpc_operation({create_subscription,_},
1326		     [Stream,Filter,StartTime,StopTime]) ->
1327    {'create-subscription',?NETCONF_NOTIF_NAMESPACE_ATTR,
1328     [{stream,[Stream]}] ++
1329	 filter(Filter) ++
1330	 maybe_element(startTime,StartTime) ++
1331	 maybe_element(stopTime,StopTime)}.
1332
1333filter(undefined) ->
1334    [];
1335filter({xpath,Filter}) when ?is_string(Filter) ->
1336    [{filter,[{type,"xpath"},{select, Filter}],[]}];
1337filter(Filter) when is_list(Filter) ->
1338    [{filter,[{type,"subtree"}],Filter}];
1339filter(Filter) ->
1340    filter([Filter]).
1341
1342maybe_element(_,undefined) ->
1343    [];
1344maybe_element(Tag,Value) ->
1345    [{Tag,[Value]}].
1346
1347%%%-----------------------------------------------------------------
1348%%% Send XML data to server
1349do_send_rpc(Op, SimpleXml, Timeout, Caller, #state{connection = Connection,
1350                                                   msg_id = MsgId,
1351                                                   pending = Pending}
1352                                            = State) ->
1353    Msg = {rpc,
1354           [{'message-id', MsgId} | ?NETCONF_NAMESPACE_ATTR],
1355           [SimpleXml]},
1356    Next = MsgId + 1,
1357    case do_send(Connection, Msg) of
1358        ok ->
1359            TRef = set_request_timer(Timeout, Op),
1360            Rec = #pending{tref = TRef,
1361                           msg_id = MsgId,
1362                           op = Op,
1363                           caller = Caller},
1364            {noreply, State#state{msg_id = Next,
1365                                  pending = [Rec | Pending]}};
1366        Error ->
1367            {reply, Error, State#state{msg_id = Next}}
1368    end.
1369
1370do_send(Connection, Simple) ->
1371    ssh_send(Connection, frame(to_xml(Simple))).
1372
1373to_xml(Simple) ->
1374    Prolog = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1375    Chars = xmerl:export_simple([Simple],
1376                                xmerl_xml,
1377                                [#xmlAttribute{name = prolog,
1378                                               value = Prolog}]),
1379    unicode:characters_to_binary(Chars).
1380
1381%% frame/1
1382
1383frame(Bin) ->
1384    case get(?KEY(chunk)) of
1385        true ->  %% 1.1 chunking
1386            [chunk(Bin) | "\n##\n"];
1387        _ ->     %% 1.0 framing
1388            [Bin | ?END_TAG]
1389    end.
1390
1391%% chunk/1
1392%%
1393%% Chunk randomly to exercise the server.
1394
1395chunk(<<>>) ->
1396    [];
1397
1398chunk(Bin) ->
1399    Sz = min(rand:uniform(1024), size(Bin)),
1400    <<B:Sz/binary, Rest/binary>> = Bin,
1401    ["\n#", integer_to_list(Sz), $\n, B | chunk(Rest)].
1402
1403%%%-----------------------------------------------------------------
1404%%% Parse and handle received XML data
1405
1406handle_data(Bin, #state{buf = Head} = S) ->
1407    case recv(Bin, Head) of
1408        {error, Reason} ->
1409            Conn = S#state.connection,
1410            ?error(Conn#connection.name, [{receive_error, Reason},
1411                                          {buffer, Head},
1412                                          {bytes, Bin}]),
1413            {stop, S};
1414        {Bytes, Rest} ->
1415            handle_more(Rest, handle_xml(Bytes, S));
1416        Buf ->
1417            {noreply, S#state{buf = Buf}}
1418    end.
1419
1420%% handle_more/2
1421
1422handle_more(_, {stop, _} = No) ->
1423    No;
1424
1425handle_more(Bin, {noreply, State}) ->
1426    handle_data(Bin, State#state{buf = true == get(?KEY(chunk))}).
1427
1428%% handle_xml/2
1429
1430handle_xml(Bytes, State) ->
1431    case parse(Bytes) of
1432        {ok, Simple, _Rest} ->  %% ignore trailing bytes
1433            decode(Simple, State);
1434        {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
1435            Conn = State#state.connection,
1436            ?error(Conn#connection.name, [{parse_error, Reason},
1437                                          {message, Bytes}]),
1438            {noreply, handle_error(Reason, State)}
1439    end.
1440
1441%% parse/1
1442
1443parse(Bytes) ->
1444    xmerl_sax_parser:stream(<<>>, [{event_fun, fun sax_event/3},
1445                                   {event_state, []},
1446                                   {continuation_fun, fun cont/1},
1447                                   {continuation_state, Bytes}]).
1448
1449%% cont/1
1450
1451cont([] = No) ->
1452    {<<>>, No};
1453
1454cont([Bin | Rest]) ->
1455    {Bin, Rest};
1456
1457cont(Bin) ->
1458    {Bin, <<>>}.
1459
1460%% handle_error/2
1461
1462handle_error(_Reason, #state{pending = []} = State) ->
1463    State;
1464
1465handle_error(Reason, #state{pending = Pending} = State) ->
1466    %% Assuming the first request gets the first answer.
1467    Rec = #pending{tref = TRef,
1468                   caller = Caller}
1469        = lists:last(Pending),
1470    cancel_request_timer(TRef),
1471    ct_gen_conn:return(Caller,{error, {failed_to_parse_received_data, Reason}}),
1472    State#state{pending = lists:delete(Rec, Pending)}.
1473
1474%% Event function for the sax parser. It builds a simple XML structure.
1475%% Care is taken to keep namespace attributes and prefixes as in the original XML.
1476sax_event(Event,_Loc,State) ->
1477    sax_event(Event,State).
1478
1479sax_event({startPrefixMapping, Prefix, Uri},Acc) ->
1480    %% startPrefixMapping will always come immediately before the
1481    %% startElement where the namespace is defined.
1482    [{xmlns,{Prefix,Uri}}|Acc];
1483sax_event({startElement,_Uri,_Name,QN,Attrs},Acc) ->
1484    %% Pick out any namespace attributes inserted due to a
1485    %% startPrefixMapping event.The rest of Acc will then be only
1486    %% elements.
1487    {NsAttrs,NewAcc} = split_attrs_and_elements(Acc,[]),
1488    Tag = qn_to_tag(QN),
1489    [{Tag,NsAttrs ++ parse_attrs(Attrs),[]}|NewAcc];
1490sax_event({endElement,_Uri,_Name,_QN},[{Name,Attrs,Cont},{Parent,PA,PC}|Acc]) ->
1491    [{Parent,PA,[{Name,Attrs,lists:reverse(Cont)}|PC]}|Acc];
1492sax_event(endDocument,[{Tag,Attrs,Cont}]) ->
1493    {Tag,Attrs,lists:reverse(Cont)};
1494sax_event({characters,String},[{Name,Attrs,Cont}|Acc]) ->
1495    [{Name,Attrs,[String|Cont]}|Acc];
1496sax_event(_Event,State) ->
1497    State.
1498
1499split_attrs_and_elements([{xmlns,{Prefix,Uri}}|Rest],Attrs) ->
1500    split_attrs_and_elements(Rest,[{xmlnstag(Prefix),Uri}|Attrs]);
1501split_attrs_and_elements(Elements,Attrs) ->
1502    {Attrs,Elements}.
1503
1504xmlnstag([]) ->
1505    xmlns;
1506xmlnstag(Prefix) ->
1507    list_to_atom("xmlns:"++Prefix).
1508
1509qn_to_tag({[],Name}) ->
1510    list_to_atom(Name);
1511qn_to_tag({Prefix,Name}) ->
1512    list_to_atom(Prefix ++ ":" ++ Name).
1513
1514parse_attrs([{_Uri, [], Name, Value}|Attrs]) ->
1515    [{list_to_atom(Name),Value}|parse_attrs(Attrs)];
1516parse_attrs([{_Uri, Prefix, Name, Value}|Attrs]) ->
1517    [{list_to_atom(Prefix ++ ":" ++ Name),Value}|parse_attrs(Attrs)];
1518parse_attrs([]) ->
1519    [].
1520
1521
1522%%%-----------------------------------------------------------------
1523
1524%% decode/2
1525%%
1526%% Decode parsed (incoming) XML.
1527
1528decode({Tag, _, _} = E, #state{} = State) ->
1529    case decode(get_local_name_atom(Tag), E, State) of
1530        #state{} = S ->
1531            {noreply, S};
1532        {stop, #state{}} = T ->
1533            T
1534    end.
1535
1536%% decode/3
1537
1538decode('rpc-reply', {_, Attrs, _} = E, State) ->
1539    decode_rpc_reply(get_msg_id(Attrs), E, State);
1540
1541%% Incoming hello, outgoing not yet sent.
1542decode(hello, E, #state{hello_status = undefined} = State) ->
1543    case decode_hello(E) of
1544        {ok, SessionId, Capabilities} ->
1545            State#state{session_id = SessionId,
1546                        capabilities = Capabilities,
1547                        hello_status = received};
1548        {error, _Reason} = No ->
1549            State#state{hello_status = No}
1550    end;
1551
1552%% Incoming hello, outgoing already sent: negotiate protocol version.
1553decode(hello, E, #state{hello_status = #pending{tref = TRef,
1554                                                caller = From}}
1555                 = State) ->
1556    cancel_request_timer(TRef),
1557    case decode_hello(E) of
1558        {ok, SessionId, Capabilities} ->
1559            reply(From, handle_capx(State#state{session_id = SessionId,
1560                                                capabilities = Capabilities,
1561                                                hello_status = received}));
1562        {error, _Reason} = No ->
1563            ct_gen_conn:return(From, No),
1564            {stop, State#state{hello_status = No}}
1565    end;
1566
1567%% Duplicate hello: ignore.
1568decode(hello, E, #state{hello_status = Other} = State) ->
1569    ConnName = (State#state.connection)#connection.name,
1570    ?error(ConnName, [{got_unexpected_hello, E},
1571                      {hello_status, Other}]),
1572    State;
1573
1574decode(notification, E, State) ->
1575    notify(State, E),
1576    State;
1577
1578decode(Other, E, State) ->
1579    decode_send({got_unexpected_msg, Other}, E, State).
1580
1581%% notify/2
1582
1583notify(#state{receivers = []} = State, E) ->
1584    Name = (State#state.connection)#connection.name,
1585    ?error(Name, [{got_unexpected_notification, E}]);
1586
1587%% Sending can fail with an atom-valued destination, but it's up to
1588%% the user.
1589notify(#state{receivers = T}, E) ->
1590    lists:foreach(fun(D) -> D ! E end, if is_pid(T) -> [T]; true -> T end).
1591
1592%% reply/2
1593%%
1594%% Explicitly send a reply that can't be returned.
1595
1596reply(From, {T, Res, State}) ->
1597    ct_gen_conn:return(From, Res),
1598    case T of
1599        reply ->
1600            State;
1601        stop ->
1602            {T, State}
1603    end.
1604
1605%% get_msg_id/1
1606
1607get_msg_id(Attrs) ->
1608    case find('message-id', Attrs) of
1609        {_,Str} ->
1610            list_to_integer(Str);
1611        false ->
1612            undefined
1613    end.
1614
1615%% recode_rpc_reply/3
1616
1617decode_rpc_reply(undefined, E, #state{pending = [#pending{msg_id = MsgId}]}
1618                               = State)
1619  when MsgId /= undefined ->
1620    ConnName = (State#state.connection)#connection.name,
1621    ?error(ConnName, [{warning, rpc_reply_missing_msg_id},
1622                      {assuming, MsgId}]),
1623    decode_rpc_reply(MsgId, E, State);
1624
1625decode_rpc_reply(undefined, _, State) ->
1626    ConnName = (State#state.connection)#connection.name,
1627    ?error(ConnName, [{error, rpc_reply_missing_msg_id}]),
1628    State;
1629
1630decode_rpc_reply(MsgId,
1631                 {_, Attrs, Content0}
1632                 = E,
1633                 #state{pending = Pending}
1634                 = State) ->
1635    case lists:keytake(MsgId, #pending.msg_id, Pending) of
1636        {value, Rec, Rest} ->
1637            #pending{tref = TRef, op = Op, caller = From}
1638                = Rec,
1639            cancel_request_timer(TRef),
1640            Content = forward_xmlns_attr(Attrs, Content0),
1641            {Reply, T} = do_decode_rpc_reply(Op,
1642                                             Content,
1643                                             State#state{pending = Rest}),
1644            ct_gen_conn:return(From, Reply),
1645            T;
1646        false ->  %% not a send_rcp or server has sent wrong id
1647            decode_send({got_unexpected_msg_id, MsgId}, E, State)
1648    end.
1649
1650%% decode_send/2
1651%%
1652%% Result of send/2,3. Only handle one at a time there since all
1653%% pendings have msg_id = undefined.
1654
1655decode_send(ErrorT, Elem, #state{pending = Pending} = State) ->
1656    case [P || #pending{msg_id = undefined} = P <- Pending] of
1657        [Rec] ->
1658            #pending{tref = TRef,
1659                     caller = From}
1660                = Rec,
1661            cancel_request_timer(TRef),
1662            ct_gen_conn:return(From, Elem),
1663            State#state{pending = lists:delete(Rec, Pending)};
1664        _ ->
1665            Conn = State#state.connection,
1666            ?error(Conn#connection.name, [ErrorT, {expecting, Pending}]),
1667            State
1668    end.
1669
1670%% do_decode_rpc_reply/3
1671
1672do_decode_rpc_reply(Op, Result, State)
1673  when Op == lock;
1674       Op == unlock;
1675       Op == edit_config;
1676       Op == delete_config;
1677       Op == copy_config;
1678       Op == kill_session ->
1679    {decode_ok(Result), State};
1680
1681do_decode_rpc_reply(Op, Result, State)
1682  when Op == get;
1683       Op == get_config;
1684       Op == action ->
1685    {decode_data(Result), State};
1686
1687do_decode_rpc_reply(close_session, Result, State) ->
1688    case decode_ok(Result) of
1689        ok ->
1690            {ok, {stop, State}};
1691        Other ->
1692            {Other, State}
1693    end;
1694
1695%% Only set a new destination if one (or more) hasn't been set with a
1696%% receiver option(), to allow more than calls to create_subscription
1697%% to order notifications.
1698do_decode_rpc_reply({create_subscription, Pid}, Result, #state{receivers = T}
1699                                                        = State) ->
1700    case decode_ok(Result) of
1701        ok when T == [];
1702                is_pid(T) ->
1703            {ok, State#state{receivers = Pid}};
1704        Other ->
1705            {Other, State}
1706    end;
1707
1708do_decode_rpc_reply(get_event_streams, Result, State) ->
1709    {decode_streams(decode_data(Result)), State};
1710
1711do_decode_rpc_reply(undefined, Result, State) ->
1712    {Result, State}.
1713
1714
1715
1716decode_ok([{Tag,Attrs,Content}]) ->
1717    case get_local_name_atom(Tag) of
1718	ok ->
1719	    ok;
1720	'rpc-error' ->
1721	    {error,forward_xmlns_attr(Attrs,Content)};
1722	_Other ->
1723	    {error,{unexpected_rpc_reply,[{Tag,Attrs,Content}]}}
1724    end;
1725decode_ok(Other) ->
1726    {error,{unexpected_rpc_reply,Other}}.
1727
1728decode_data([{Tag,Attrs,Content}]) ->
1729    case get_local_name_atom(Tag) of
1730	ok ->
1731	    %% when action has return type void
1732	    ok;
1733	data ->
1734	    %% Since content of data has nothing from the netconf
1735	    %% namespace, we remove the parent's xmlns attribute here
1736	    %% - just to make the result cleaner
1737	    {ok,forward_xmlns_attr(remove_xmlnsattr_for_tag(Tag,Attrs),Content)};
1738	'rpc-error' ->
1739	    {error,forward_xmlns_attr(Attrs,Content)};
1740	_Other ->
1741	    {error,{unexpected_rpc_reply,[{Tag,Attrs,Content}]}}
1742    end;
1743decode_data(Other) ->
1744    {error,{unexpected_rpc_reply,Other}}.
1745
1746get_qualified_name(Tag) ->
1747    case string:lexemes(atom_to_list(Tag),":") of
1748	[TagStr] -> {[],TagStr};
1749	[PrefixStr,TagStr] -> {PrefixStr,TagStr}
1750    end.
1751
1752get_local_name_atom(Tag) ->
1753    {_,TagStr} = get_qualified_name(Tag),
1754    list_to_atom(TagStr).
1755
1756
1757%% Remove the xmlns attr that points to the tag. I.e. if the tag has a
1758%% prefix, remove {'xmlns:prefix',_}, else remove default {xmlns,_}.
1759remove_xmlnsattr_for_tag(Tag,Attrs) ->
1760    {Prefix,_TagStr} = get_qualified_name(Tag),
1761    lists:keydelete(xmlnstag(Prefix), 1, Attrs).
1762
1763%% Prepend xmlns attributes from parent to children, omitting those
1764%% the child sets.
1765forward_xmlns_attr(ParentAttrs, Children) ->
1766    Namespace = lists:filter(fun is_xmlns/1, ParentAttrs),
1767    [{T, Ns ++ A, C} || {T, A, C} <- Children,
1768                        F <- [fun({K,_}) -> not lists:keymember(K, 1, A) end],
1769                        Ns <- [lists:filter(F, Namespace)]].
1770
1771is_xmlns({Key, _}) ->
1772    Key == xmlns orelse lists:prefix("xmlns:", atom_to_list(Key)).
1773
1774%% Decode server hello to pick out session id and capabilities
1775decode_hello({hello, _Attrs, Hello}) ->
1776    U = make_ref(),
1777    try
1778        [{'session-id', _, [SessionId]}, _ | _]
1779            = [find('session-id', Hello), no_session_id_found | U],
1780        [{ok, Id}, _ | _]
1781            = [catch {ok, list_to_integer(SessionId)}, invalid_session_id | U],
1782        [true, _ | _]
1783            = [0 < Id, invalid_session_id | U],
1784        [{capabilities, _, Capabilities}, _ | _]
1785            = [find(capabilities, Hello), capabilities_not_found | U],
1786        [{ok, Caps}, _ | _]
1787            = [decode_caps(Capabilities, [], false), false | U],
1788        {ok, Id, Caps}
1789    catch
1790        error: {badmatch, [Error, false | U]} ->
1791            Error;
1792        error: {badmatch, [_, Reason | U]} ->
1793            {error, {incorrect_hello, Reason}}
1794    end.
1795
1796find(Key, List) ->
1797    lists:keyfind(Key, 1, List).
1798
1799decode_caps([{capability, [], [?NETCONF_BASE_CAP ++ _ = Cap]} | Caps],
1800            Acc,
1801            _) ->
1802    decode_caps(Caps, [Cap|Acc], true);
1803decode_caps([{capability, [], [Cap]} | Caps], Acc, Base) ->
1804    decode_caps(Caps, [Cap|Acc], Base);
1805decode_caps([H|_], _, _) ->
1806    {error, {unexpected_capability_element, H}};
1807decode_caps([], _, false) ->
1808    {error, {incorrect_hello, no_base_capability_found}};
1809decode_caps([], Acc, true) ->
1810    {ok, lists:reverse(Acc)}.
1811
1812
1813%% Return a list of {Name,Data}, where data is a {Tag,Value} list for each stream
1814decode_streams({error,Reason}) ->
1815    {error,Reason};
1816decode_streams({ok,[{netconf,_,Streams}]}) ->
1817    {ok,decode_streams(Streams)};
1818decode_streams([{streams,_,Streams}]) ->
1819    decode_streams(Streams);
1820decode_streams([{stream,_,Stream} | Streams]) ->
1821    {name,_,[Name]} = find(name, Stream),
1822    [{Name,[{Tag,Value} || {Tag,_,[Value]} <- Stream, Tag /= name]}
1823     | decode_streams(Streams)];
1824decode_streams([]) ->
1825    [].
1826
1827
1828%%%-----------------------------------------------------------------
1829%%% Logging
1830
1831log(Connection,Action) ->
1832    log(Connection,Action,<<>>).
1833log(#connection{reference=Ref,host=Host,port=Port,name=Name},Action,Data) ->
1834    Address =
1835        case Ref of
1836            {_,Ch} -> {Host,Port,Ch};
1837            _ -> {Host,Port}
1838        end,
1839    error_logger:info_report(#conn_log{client=self(),
1840				       address=Address,
1841				       name=Name,
1842				       action=Action,
1843				       module=?MODULE},
1844			     Data).
1845
1846
1847%% Log callback - called from the error handler process
1848format_data(How,Data) ->
1849    %% Assuming that the data is encoded as UTF-8.  If it is not, then
1850    %% the printout might be wrong, but the format function will not
1851    %% crash!
1852    %% FIXME: should probably read encoding from the data and do
1853    %% unicode:characters_to_binary(Data,InEncoding,utf8) when calling
1854    %% log/3 instead of assuming utf8 in as done here!
1855    do_format_data(How,unicode:characters_to_binary(Data)).
1856
1857do_format_data(raw,Data) ->
1858    io_lib:format("~n~ts~n",[hide_password(Data)]);
1859do_format_data(pretty,Data) ->
1860    maybe_io_lib_format(indent(Data));
1861do_format_data(html,Data) ->
1862    maybe_io_lib_format(html_format(Data)).
1863
1864maybe_io_lib_format(<<>>) ->
1865    [];
1866maybe_io_lib_format(String) ->
1867    io_lib:format("~n~ts~n",[String]).
1868
1869%%%-----------------------------------------------------------------
1870%%% Hide password elements from XML data
1871hide_password(Bin) ->
1872    re:replace(Bin,<<"(<password[^>]*>)[^<]*(</password>)">>,<<"\\1*****\\2">>,
1873	       [global,{return,binary},unicode]).
1874
1875%%%-----------------------------------------------------------------
1876%%% HTML formatting
1877html_format(Bin) ->
1878    binary:replace(indent(Bin),<<"<">>,<<"&lt;">>,[global]).
1879
1880%%%-----------------------------------------------------------------
1881%%% Indentation of XML code
1882indent(Bin) ->
1883    String = normalize(hide_password(Bin)),
1884    IndentedString =
1885	case erase(part_of_line) of
1886	    undefined ->
1887		indent1(String,[]);
1888	    Part ->
1889		indent1(lists:reverse(Part)++String,erase(indent))
1890	end,
1891    unicode:characters_to_binary(IndentedString).
1892
1893%% Normalizes the XML document by removing all space and newline
1894%% between two XML tags.
1895%% Returns a list, no matter if the input was a list or a binary.
1896normalize(Bin) ->
1897    re:replace(Bin,<<">[ \r\n\t]+<">>,<<"><">>,[global,{return,list},unicode]).
1898
1899
1900indent1("<?"++Rest1,Indent1) ->
1901    %% Prolog
1902    {Line,Rest2,Indent2} = indent_line(Rest1,Indent1,[$?,$<]),
1903    Line++indent1(Rest2,Indent2);
1904indent1("</"++Rest1,Indent1) ->
1905    %% Stop tag
1906    case indent_line1(Rest1,Indent1,[$/,$<]) of
1907	{[],[],_} ->
1908	    [];
1909	{Line,Rest2,Indent2} ->
1910	    "\n"++Line++indent1(Rest2,Indent2)
1911    end;
1912indent1("<"++Rest1,Indent1) ->
1913    %% Start- or empty tag
1914    put(tag,get_tag(Rest1)),
1915    case indent_line(Rest1,Indent1,[$<]) of
1916	{[],[],_} ->
1917	    [];
1918	{Line,Rest2,Indent2} ->
1919	    "\n"++Line++indent1(Rest2,Indent2)
1920    end;
1921indent1([H|T],Indent) ->
1922    [H|indent1(T,Indent)];
1923indent1([],_Indent) ->
1924    [].
1925
1926indent_line("?>"++Rest,Indent,Line) ->
1927    %% Prolog
1928    {lists:reverse(Line)++"?>",Rest,Indent};
1929indent_line("/></"++Rest,Indent,Line) ->
1930    %% Empty tag, and stop of parent tag -> one step out in indentation
1931    {Indent++lists:reverse(Line)++"/>","</"++Rest,Indent--"  "};
1932indent_line("/>"++Rest,Indent,Line) ->
1933    %% Empty tag, then probably next tag -> keep indentation
1934    {Indent++lists:reverse(Line)++"/>",Rest,Indent};
1935indent_line("></"++Rest,Indent,Line) ->
1936    LastTag = erase(tag),
1937    case get_tag(Rest) of
1938	LastTag ->
1939	    %% Start and stop tag, but no content
1940	    indent_line1(Rest,Indent,[$/,$<,$>|Line]);
1941	_ ->
1942	    %% Stop tag completed, and then stop tag of parent -> one step out
1943	    {Indent++lists:reverse(Line)++">","</"++Rest,Indent--"  "}
1944    end;
1945indent_line("><"++Rest,Indent,Line) ->
1946    %% Stop tag completed, and new tag comming -> keep indentation
1947    {Indent++lists:reverse(Line)++">","<"++Rest,"  "++Indent};
1948indent_line("</"++Rest,Indent,Line) ->
1949    %% Stop tag starting -> search for end of this tag
1950    indent_line1(Rest,Indent,[$/,$<|Line]);
1951indent_line([H|T],Indent,Line) ->
1952    indent_line(T,Indent,[H|Line]);
1953indent_line([],Indent,Line) ->
1954    %% The line is not complete - will be continued later
1955    put(part_of_line,Line),
1956    put(indent,Indent),
1957    {[],[],Indent}.
1958
1959indent_line1("></"++Rest,Indent,Line) ->
1960    %% Stop tag completed, and then stop tag of parent -> one step out
1961    {Indent++lists:reverse(Line)++">","</"++Rest,Indent--"  "};
1962indent_line1(">"++Rest,Indent,Line) ->
1963    %% Stop tag completed -> keep indentation
1964    {Indent++lists:reverse(Line)++">",Rest,Indent};
1965indent_line1([H|T],Indent,Line) ->
1966    indent_line1(T,Indent,[H|Line]);
1967indent_line1([],Indent,Line) ->
1968    %% The line is not complete - will be continued later
1969    put(part_of_line,Line),
1970    put(indent,Indent),
1971    {[],[],Indent}.
1972
1973get_tag("/>"++_) ->
1974    [];
1975get_tag(">"++_) ->
1976    [];
1977get_tag([H|T]) ->
1978    [H|get_tag(T)];
1979get_tag([]) ->
1980    %% The line is not complete - will be continued later.
1981    [].
1982
1983
1984%%%-----------------------------------------------------------------
1985%%% SSH stuff
1986ssh_connect(#options{host=Host,timeout=Timeout,port=Port,
1987                     ssh=SshOpts,name=Name,type=Type}) ->
1988    case ssh:connect(Host, Port,
1989		     [{user_interaction,false},
1990		      {silently_accept_hosts, true}|SshOpts],
1991                     Timeout) of
1992	{ok,CM} ->
1993            Connection = #connection{reference = CM,
1994                                     host = Host,
1995                                     port = Port,
1996                                     name = Name,
1997                                     type = Type},
1998            log(Connection,connect),
1999            {ok,Connection};
2000	{error,Reason} ->
2001	    {error,{ssh,could_not_connect_to_server,Reason}}
2002    end.
2003
2004ssh_channel(#connection{reference=CM}=Connection0,
2005            #options{timeout=Timeout,name=Name,type=Type}) ->
2006    case ssh_connection:session_channel(CM, Timeout) of
2007        {ok,Ch} ->
2008            case ssh_connection:subsystem(CM, Ch, "netconf", Timeout) of
2009                success ->
2010                    Connection = Connection0#connection{reference = {CM,Ch},
2011                                                       name = Name,
2012                                                       type = Type},
2013                    log(Connection,open),
2014                    {ok, Connection};
2015                failure ->
2016                    ssh_connection:close(CM,Ch),
2017                    {error,{ssh,could_not_execute_netconf_subsystem}};
2018                {error,timeout} ->
2019                    ssh_connection:close(CM,Ch),
2020                    {error,{ssh,could_not_execute_netconf_subsystem,timeout}}
2021            end;
2022        {error, Reason} ->
2023            {error,{ssh,could_not_open_channel,Reason}}
2024    end.
2025
2026
2027ssh_open(Options) ->
2028    case ssh_connect(Options) of
2029        {ok,Connection} ->
2030            case ssh_channel(Connection,Options) of
2031                {ok,_} = Ok ->
2032                    Ok;
2033                Error ->
2034                    ssh_close(Connection),
2035                    Error
2036            end;
2037        Error ->
2038            Error
2039    end.
2040
2041ssh_send(#connection{reference = {CM,Ch}}=Connection, Data) ->
2042    case ssh_connection:send(CM, Ch, Data) of
2043	ok ->
2044            log(Connection,send,Data),
2045            ok;
2046	{error,Reason} ->
2047            {error,{ssh,failed_to_send_data,Reason}}
2048    end.
2049
2050ssh_close(Connection=#connection{reference = {CM,Ch}, type = Type}) ->
2051    _ = ssh_connection:close(CM,Ch),
2052    log(Connection,close),
2053    case Type of
2054        connection_and_channel ->
2055            ssh_close(Connection#connection{reference = CM});
2056        _ ->
2057            ok
2058    end,
2059    ok;
2060ssh_close(Connection=#connection{reference = CM}) ->
2061    _ = ssh:close(CM),
2062    log(Connection,disconnect),
2063    ok.
2064
2065%% ===========================================================================
2066
2067%% recv/1
2068%%
2069%% Extract incoming messages using either NETCONF 1.0 framing or
2070%% NETCONF 1.1 chunking.
2071
2072recv(Bin, true) ->
2073    recv(Bin, [<<>>, 3]);
2074recv(Bin, false) ->
2075    recv(Bin, <<>>);
2076
2077recv(Bin, [Head, Len | Chunks]) ->       %% 1.1 chunking
2078    chunk(<<Head/binary, Bin/binary>>, Chunks, Len);
2079
2080%% Start looking for the terminating end-of-message sequence ]]>]]>
2081%% 5 characters from the end of the buffered head, since this binary
2082%% has already been scanned.
2083recv(Bin, Head) when is_binary(Head) ->  %% 1.0 framing
2084    frame(<<Head/binary, Bin/binary>>, max(0, size(Head) - 5)).
2085
2086%% frame/2
2087%%
2088%% Extract a message terminated by the ]]>]]> end-of-message sequence.
2089%% Don't need to extract characters as UTF-8 since matching byte-wise
2090%% is unambiguous: the high-order bit of every byte of a multi-byte
2091%% UTF character is 1, while the end-of-message sequence is ASCII.
2092
2093frame(Bin, Start) ->
2094    Sz = size(Bin),
2095    Scope = {Start, Sz - Start},
2096    case binary:match(Bin, pattern(), [{scope, Scope}]) of
2097        {Len, 6} ->
2098            <<Msg:Len/binary, _:6/binary, Rest/binary>> = Bin,
2099            {trim(Msg), Rest};
2100        nomatch ->
2101            Bin
2102    end.
2103
2104%% pattern/0
2105
2106pattern() ->
2107    Key = ?KEY(pattern),
2108    case get(Key) of
2109        undefined ->
2110            CP = binary:compile_pattern(<<"]]>]]>">>),
2111            put(Key, CP),
2112            CP;
2113        CP ->
2114            CP
2115    end.
2116
2117%% trim/1
2118%%
2119%% Whitespace before an XML declaration is an error, but be somewhat
2120%% lenient and strip line breaks since the RFC's are unclear on what's
2121%% allowed following a ]]>]]> delimiter. Typical seems to be a single
2122%% $\n, but strip any of " \t\r\n", and regardless of NETCONF version.
2123
2124trim(<<C, Bin/binary>>)
2125  when C == $\n;
2126       C == $\r;
2127       C == $\t;
2128       C == $  ->
2129    trim(Bin);
2130
2131trim(Bin) ->
2132    Bin.
2133
2134%% chunk/3
2135%%
2136%% The final argument is either 0 to indicate that a specified number
2137%% of bytes of chunk data should be consumed, or at least 3 to
2138%% indicate an offset at which to look for a newline following a chunk
2139%% size.
2140
2141%% Accumulating chunk-data ...
2142chunk(Bin, [Sz | Chunks] = L, 0) ->
2143    case Bin of
2144        <<Chunk:Sz/binary, Rest/binary>> ->
2145            chunk(Rest, acc(Chunk, Chunks), 3);  %% complete chunk ...
2146        _ ->
2147            [Bin, 0 | L]                         %% ... or not
2148    end;
2149
2150%% ... or a header.
2151
2152chunk(Bin, Chunks, Len)
2153  when size(Bin) < 4 ->
2154    [Bin, 3 = Len | Chunks];
2155
2156%% End of chunks.
2157chunk(<<"\n##\n", Rest/binary>>, Chunks, _) ->
2158    case Chunks of
2159        [] ->
2160            {error, "end-of-chunks unexpected"}; %% must be at least one
2161        Bins ->
2162            {lists:reverse(Bins), Rest}
2163    end;
2164
2165%% Matching each of the 10 newline possibilities is faster than
2166%% searching.
2167chunk(<<"\n#", Head:1/binary, $\n, Rest/binary>>, Chunks, _) ->
2168    acc(Head, Rest, Chunks);
2169chunk(<<"\n#", Head:2/binary, $\n, Rest/binary>>, Chunks, _) ->
2170    acc(Head, Rest, Chunks);
2171chunk(<<"\n#", Head:3/binary, $\n, Rest/binary>>, Chunks, _) ->
2172    acc(Head, Rest, Chunks);
2173chunk(<<"\n#", Head:4/binary, $\n, Rest/binary>>, Chunks, _) ->
2174    acc(Head, Rest, Chunks);
2175chunk(<<"\n#", Head:5/binary, $\n, Rest/binary>>, Chunks, _) ->
2176    acc(Head, Rest, Chunks);
2177chunk(<<"\n#", Head:6/binary, $\n, Rest/binary>>, Chunks, _) ->
2178    acc(Head, Rest, Chunks);
2179chunk(<<"\n#", Head:7/binary, $\n, Rest/binary>>, Chunks, _) ->
2180    acc(Head, Rest, Chunks);
2181chunk(<<"\n#", Head:8/binary, $\n, Rest/binary>>, Chunks, _) ->
2182    acc(Head, Rest, Chunks);
2183chunk(<<"\n#", Head:9/binary, $\n, Rest/binary>>, Chunks, _) ->
2184    acc(Head, Rest, Chunks);
2185chunk(<<"\n#", Head:10/binary, $\n, Rest/binary>>, Chunks, _) ->
2186    acc(Head, Rest, Chunks);
2187
2188chunk(<<"\n#", Bin:11/binary, _/binary>>, _, _) ->
2189    {error, {"chunk-size too long", Bin}}; %% 32-bits = max 10 digits
2190
2191chunk(<<"\n#", _/binary>> = Bin, Chunks, _) ->
2192    [Bin, size(Bin) | Chunks];
2193
2194chunk(Bin, Chunks, 3 = Len) ->
2195    case drop(Bin) of
2196        <<>> ->
2197            [Bin, Len | Chunks];
2198        <<"\n#", _/binary>> = B ->
2199            chunk(B, Chunks, Len);
2200        _ ->
2201            {error, {"not a chunk", Bin}}
2202    end.
2203
2204%% drop/1
2205
2206drop(<<"\n#", _/binary>> = Bin) ->
2207    Bin;
2208
2209drop(<<C, Bin/binary>>)
2210  when C == $\n;
2211       C == $\r;
2212       C == $\t;
2213       C == $  ->
2214    drop(Bin);
2215
2216drop(Bin) ->
2217    Bin.
2218
2219%% acc/2
2220
2221acc(Chunk, []) ->
2222    [B || B <- [trim(Chunk)], <<>> /= B];
2223
2224acc(Chunk, Chunks) ->
2225    [Chunk | Chunks].
2226
2227%% acc/3
2228
2229acc(Head, Rest, Chunks) ->
2230    case chunk_size(Head) of
2231        {error, _Reason} = No ->
2232            No;
2233        Sz ->
2234            chunk(Rest, [Sz | Chunks], 0)
2235    end.
2236
2237%% chunk_size/1
2238
2239chunk_size(<<C, _/binary>> = Bin) ->
2240    try true = $0 < C, binary_to_integer(Bin) of
2241        Sz when 0 < Sz bsr 32 ->
2242            {error, {"chunk-size too large", Sz}};
2243        Sz ->
2244            Sz
2245    catch
2246        error: _ ->
2247            {error, {"chunk-size invalid", Bin}}
2248    end.
2249
2250%%----------------------------------------------------------------------
2251%% END OF MODULE
2252%%----------------------------------------------------------------------
2253