File 5201-public_key-pubkey_policy_tree_SUITE-and-tree-visuali.patch of Package erlang

From bdfd1616de36aec8af8d3b91962da9319c255bf0 Mon Sep 17 00:00:00 2001
From: Jakub Witczak <kuba@erlang.org>
Date: Sat, 14 Oct 2023 20:18:32 +0200
Subject: [PATCH 1/2] public_key: pubkey_policy_tree_SUITE and tree
 visualization

---
 lib/public_key/test/Makefile                  |   3 +-
 .../test/pubkey_policy_tree_SUITE.erl         | 350 ++++++++++++++++++
 2 files changed, 352 insertions(+), 1 deletion(-)
 create mode 100644 lib/public_key/test/pubkey_policy_tree_SUITE.erl

diff --git a/lib/public_key/test/Makefile b/lib/public_key/test/Makefile
index 57879c20e6..239e078193 100644
--- a/lib/public_key/test/Makefile
+++ b/lib/public_key/test/Makefile
@@ -33,7 +33,8 @@ MODULES= \
 	public_key_SUITE \
 	pbe_SUITE \
 	pkits_SUITE \
-	pubkey_cert_SUITE
+	pubkey_cert_SUITE \
+	pubkey_policy_tree_SUITE
 
 ERL_FILES= $(MODULES:%=%.erl)
 
diff --git a/lib/public_key/test/pubkey_policy_tree_SUITE.erl b/lib/public_key/test/pubkey_policy_tree_SUITE.erl
new file mode 100644
index 0000000000..0e4004baf8
--- /dev/null
+++ b/lib/public_key/test/pubkey_policy_tree_SUITE.erl
@@ -0,0 +1,350 @@
+-module(pubkey_policy_tree_SUITE).
+-compile([export_all, nowarn_export_all]).
+
+-include_lib("stdlib/include/assert.hrl").
+
+-define(PRE_SCRIPT, "<pre class=\"mermaid\">~n").
+-define(POST_SCRIPT, "~n</pre><script type=\"module\">import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';</script>").
+
+%% -define(DEBUG, true).
+
+-ifdef(DEBUG).
+-define(FWR(Fmt, Args),
+        begin
+            io:fwrite(user, "dbg: " ++ Fmt, Args)
+        end).
+-else.
+-define(FWR(Fmt, Args), ok).
+-endif.
+
+-define(PAL_MMD(MMD),
+       begin
+           %% ?FWR("~s~n", [MMD]),
+           ct:log(MMD),
+           ?FWR("~n", [])
+       end).
+-define(ANY_POLICY_OID, {2,5,29,32,0}).
+-define(ROOT_TN, pubkey_policy_tree:root()).
+-define(EMPTY_VPT, {}).
+-define(ROOT_PN,
+        begin
+            {AnyPolicyNode, _} = ?ROOT_TN,
+            AnyPolicyNode
+            %% ?PN(?ANY_POLICY_OID)
+        end).
+-define(PN(VP), pubkey_policy_tree:policy_node(VP, [], [])).
+-define(PN(VP, EPS), pubkey_policy_tree:policy_node(VP, [], EPS)).
+
+%% Note: trees used for testing below might not make much sense from
+%% RFC5280 perspective and can be improved for sure; but can be good
+%% enough for checking some general tree handling code
+
+all() -> [add_leaves,
+          add_leaf_siblings,
+          all_leaves,
+          prune_leaves,
+          prune_tree_and_leaves,
+          constrained_policy_node_set,
+          valid_policy_node_set,
+          any_leaves,
+          prune_tree_shorter_branch,
+          prune_invalid_nodes].
+
+any_leaves(_Config) ->
+    AL =
+        fun(Tree) ->
+                pubkey_policy_tree:any_leaves(Tree)
+        end,
+    ?assertEqual([], AL(?EMPTY_VPT)),
+    TreeWithAnyPolicyLeaf = {?ROOT_PN,
+                             [{?PN("GOLD"),
+                               [?PN(?ANY_POLICY_OID)]}]},
+    log_tree_diagram("TreeWithAnyPolicyLeaf", TreeWithAnyPolicyLeaf),
+    ?assertEqual([?PN(?ANY_POLICY_OID)], AL(TreeWithAnyPolicyLeaf)),
+    TreeWithAnyPolicyNode1 = {?ROOT_PN,
+                             [{?PN(?ANY_POLICY_OID),
+                               [?PN("GOLD")]},
+                              {?PN("SILVER", ["A"]),
+                               [?PN("SILVER", ["B"])]}]},
+    log_tree_diagram("TreeWithAnyPolicyNode1", TreeWithAnyPolicyNode1),
+    ?assertEqual([], AL(TreeWithAnyPolicyNode1)),
+    ok.
+
+constrained_policy_node_set(_Config) ->
+    TreeWithAnyPolicyLeaf = {?ROOT_PN,
+                             [{?PN("GOLD"),
+                               [?PN(?ANY_POLICY_OID)]}]},
+    TreeWithAnyPolicyNode1 = {?ROOT_PN,
+                             [{?PN(?ANY_POLICY_OID),
+                               [?PN("GOLD")]},
+                              {?PN("SILVER", ["A"]),
+                               [?PN("SILVER", ["B"])]}]},
+    TreeWithAnyPolicyNode2 = {?ROOT_PN,
+                             [{?PN(?ANY_POLICY_OID),
+                               [{?PN("GOLD", ["GOLD", "SILVER"]),
+                                 [?PN("SILVER")]}]}]},
+    CS =
+        fun(Tree) ->
+                pubkey_policy_tree:constrained_policy_node_set(Tree)
+        end,
+    ?assertEqual([], CS(?EMPTY_VPT)),
+    ?assertEqual([?PN(?ANY_POLICY_OID)], CS(TreeWithAnyPolicyLeaf)),
+    ?assertEqual([?PN("SILVER", ["A"]), ?PN("GOLD")], CS(TreeWithAnyPolicyNode1)),
+    ?assertEqual([?PN("GOLD", ["GOLD", "SILVER"])], CS(TreeWithAnyPolicyNode2)),
+    ok.
+
+valid_policy_node_set(_Config) ->
+    VS =
+        fun(Tree) ->
+                pubkey_policy_tree:valid_policy_node_set(Tree)
+        end,
+    ?assertEqual([], VS(?EMPTY_VPT)),
+    TreeWithAnyPolicyLeaf = {?ROOT_PN,
+                             [{?PN("GOLD"),
+                               [?PN(?ANY_POLICY_OID)]}]},
+    log_tree_diagram("TreeWithAnyPolicyLeaf", TreeWithAnyPolicyLeaf),
+    ?assertEqual([?PN("GOLD")], VS(TreeWithAnyPolicyLeaf)),
+    TreeWithAnyPolicyNode1 = {?ROOT_PN,
+                             [{?PN(?ANY_POLICY_OID),
+                               [?PN("GOLD")]},
+                              {?PN("SILVER", ["A"]),
+                               [?PN("SILVER", ["B"])]}]},
+    log_tree_diagram("TreeWithAnyPolicyNode1", TreeWithAnyPolicyNode1),
+    ?assertEqual([?PN(?ANY_POLICY_OID), ?PN("SILVER", ["A"]), ?PN("GOLD")],
+                 VS(TreeWithAnyPolicyNode1)),
+    TreeWithAnyPolicyNode2 = {?ROOT_PN,
+                             [{?PN(?ANY_POLICY_OID),
+                               [{?PN("GOLD", ["GOLD", "SILVER"]),
+                                 [?PN("SILVER")]}]}]},
+    log_tree_diagram("TreeWithAnyPolicyNode2", TreeWithAnyPolicyNode2),
+    ?assertEqual([?PN(?ANY_POLICY_OID), ?PN("GOLD", ["GOLD", "SILVER"])],
+                 VS(TreeWithAnyPolicyNode2)),
+    ok.
+
+prune_invalid_nodes(_Config) ->
+    Children0 = [{?PN("GOLD"), [?PN("GOLD")]},
+                 {?PN("BLUE"), []}, % policy_tree_node type terminating branch - pruning target
+                 ?PN("SILVER")], % leaf of policy_node type
+    Tree0 = {?ROOT_PN, Children0 ++
+                 [{?PN("GOLD"), [?ROOT_PN]}]},
+    Tree0 = pubkey_policy_tree:prune_invalid_nodes(Tree0, []),
+
+    {ok, Tree1} = explain(Tree0,
+                          [{prune_invalid_nodes, [[?PN("SILVER")]]},
+                           {prune_invalid_nodes, [[?PN("BLUE")]]},
+                           {prune_invalid_nodes, [[?PN("GOLD")]]}]),
+    ?EMPTY_VPT = Tree1,
+
+    %% prune node
+    Tree2 = {?ROOT_PN, Children0 ++
+                 [{?PN(?ANY_POLICY_OID), [?ROOT_PN]}]},
+    {ok, Tree3} = explain(Tree2,
+                          [{prune_invalid_nodes, [[?PN("SILVER")]]},
+                           {prune_invalid_nodes, [[?PN("BLUE")]]},
+                           {prune_invalid_nodes, [[?PN("GOLD")]]},
+                           {prune_invalid_nodes, [[?PN(?ANY_POLICY_OID)]]},
+                           {prune_invalid_nodes, [[?ROOT_PN]]}]),
+    Expected = {?ROOT_PN, [{?PN(?ANY_POLICY_OID),
+                            [?ROOT_PN]}]},
+    ?assertEqual(Expected, Tree3),
+    ok.
+
+prune_leaves(_Config) ->
+    NullVPT = pubkey_policy_tree:empty(),
+    ?assertEqual(NullVPT, pubkey_policy_tree:prune_leaves(NullVPT, null)),
+    Tree0 = {?ROOT_PN,
+             [{?PN("GOLD"), [?PN("GOLD"), ?PN("GOLD2")]},
+              {?PN("GOLD"), [?PN(?ANY_POLICY_OID), ?PN(?ANY_POLICY_OID)]}]},
+    {ok, Tree} = explain(Tree0, [{prune_leaves, ["GOLD"]},
+                                  {prune_leaves, [?ANY_POLICY_OID]}]),
+    ?assertEqual({?ROOT_PN,
+             [{?PN("GOLD"), [?PN("GOLD2")]},
+              {?PN("GOLD"), []}]}, Tree),
+    ok.
+
+prune_tree_and_leaves(_Config) ->
+    Tree0 = {?ROOT_PN,
+             [{?PN("SILVER", ["SILVER"]),
+               []},
+              {?PN("GOLD", ["GOLD"]),
+               [?PN("GOLD", ["GOLD"])]}]},
+    Instructions = [{prune_tree, []},
+                    {prune_leaves, ["GOLD"]}],
+    {ok, Tree} = explain(Tree0, Instructions),
+    Expected = {?ROOT_PN,
+                [{?PN("GOLD", ["GOLD"]), []}]},
+    Expected = Tree,
+    ok.
+
+prune_tree_shorter_branch(_Config) ->
+    %% shorter branch ending with leaf is not expected to be pruned
+    Tree0 = {?ROOT_PN,
+          [{?PN("GOLD"), [?PN("GOLD")]},
+           ?PN("SILVER")]},
+    Instructions = [{prune_tree, []}],
+    {ok, Tree0} = explain(Tree0, Instructions),
+    ok.
+
+all_leaves(_Config) ->
+    Tree1 = {?ROOT_PN,
+     [{?PN("GOLD"),
+       [{?PN("GOLD"), []},
+        {?PN("SILVER"), [?PN("GOLD"), ?PN("SILVER")]}]},
+      {?PN("SILVER"),
+       [{?PN("GOLD"), []},
+        {?PN("SILVER"), [?PN("GOLD"), ?PN("SILVER")]}]}]},
+    log_tree_diagram("Tree1", Tree1),
+    ?assertEqual([?PN("GOLD"), ?PN("SILVER"), ?PN("GOLD"), ?PN("SILVER")],
+                 pubkey_policy_tree:all_leaves(Tree1)),
+    ok.
+
+add_leaves(_Config) ->
+    RootTree = pubkey_policy_tree:root(),
+    AddLeavesFun1 =
+        fun(_) -> [?PN("GOLD"), ?PN("SILVER")] end,
+    AddLeavesFun2 =
+        fun(#{valid_policy := "SILVER"}) ->
+                [?PN("GOLD"), ?PN("SILVER")];
+           (_) ->
+                []
+        end,
+    Instructions = [{add_leaves, [AddLeavesFun1]},
+                    {add_leaves, [AddLeavesFun1]},
+                    {add_leaves, [AddLeavesFun2]}],
+    {ok, Tree} = explain(RootTree, Instructions),
+    ?assertEqual({?ROOT_PN,
+                  [{?PN("GOLD"),
+                    [{?PN("GOLD"), []},
+                     {?PN("SILVER"), [?PN("GOLD"), ?PN("SILVER")]}]},
+                   {?PN("SILVER"),
+                    [{?PN("GOLD"), []},
+                     {?PN("SILVER"), [?PN("GOLD"), ?PN("SILVER")]}]}]},
+                 Tree),
+    ok.
+
+add_leaf_siblings(_Config) ->
+    RootTree = pubkey_policy_tree:root(),
+    AddLeavesFun1 =
+        fun(_) -> [?PN("GOLD"), ?PN("SILVER")] end,
+    AddLeavesFun2 =
+        fun(#{valid_policy := ?ANY_POLICY_OID}) ->
+                [?PN("GOLD"), ?PN("SILVER")];
+           (_) ->
+                []
+        end,
+    Instructions = [{add_leaf_siblings, [AddLeavesFun1]},
+                    {add_leaf_siblings, [AddLeavesFun1]},
+                    {add_leaf_siblings, [AddLeavesFun2]}
+                   ],
+    {ok, Tree} = explain(RootTree, Instructions),
+    ?assertEqual({?ROOT_PN,
+                  [?PN("GOLD"), ?PN("SILVER"),
+                   ?PN("GOLD"), ?PN("SILVER"),
+                   ?PN("GOLD"), ?PN("SILVER")]},
+                 Tree),
+    ok.
+
+%%--------------------------------------------------------------------
+%% Internal API
+%%--------------------------------------------------------------------
+explain(InitTree, Instructions) ->
+    ct:log("=============================================~nSTEP: 0)"),
+    ct:log("Instructions=~n~p", [Instructions]),
+    ?PAL_MMD(to_mmd("0) Initial tree", InitTree)),
+    explain(InitTree, Instructions, 1).
+
+explain(Tree, [], _) ->
+    {ok, Tree};
+explain(Tree0, [{FunctionName, Args} | Rest], N) ->
+    Title = io_lib:format("~p) pubkey_policy_tree:~p()", [N, FunctionName]),
+    ct:log("=============================================~nSTEP: ~s", [Title]),
+    Tree = apply(pubkey_policy_tree, FunctionName, [Tree0 | Args]),
+    ?PAL_MMD(to_mmd(Title, Tree)),
+    explain(Tree, Rest, N+1).
+
+log_tree_diagram(Title, Tree) ->
+    ?PAL_MMD(to_mmd(Title, Tree)),
+    ok.
+
+to_mmd(Title, Tree) ->
+    ct:log("Diagram input tree~n~p", [Tree]),
+    {NodeInfo, Arrows} = traverse(Tree),
+    ?PRE_SCRIPT ++
+        "---~ntitle: " ++ Title ++ "~n---~n" ++
+        "flowchart TD~n" ++
+         NodeInfo ++ "~n" ++ Arrows ++ "~n"
+        ?POST_SCRIPT.
+
+traverse(TreeNode = {_PolicyNode, _NoChildren = []}) ->
+    Path = [1],
+    NodeId = nid(Path),
+    ?FWR("Node (no children): ~p~n", [lists:reverse(Path)]),
+    {"", io_lib:format("~s~s~n", [NodeId, node_desc(NodeId, TreeNode)])};
+traverse(TreeNode = {}) ->
+    Path = [1],
+    NodeId = nid(Path),
+    ?FWR("NULL tree: ~p~n", [lists:reverse(Path)]),
+    {"", io_lib:format("~s~s~n", [NodeId, node_desc(NodeId, TreeNode)])};
+traverse(Tree) ->
+    traverse(Tree, [1], {#{}, []}).
+
+traverse(#{}, _Path, Acc) ->
+    ?FWR("Node (leaf found): ~p~n", [lists:reverse(_Path)]),
+    Acc;
+traverse({_Node, _NoChildren = []}, _Path = [1], _Acc = {NodeInfo0, Arrows}) ->
+    %% traverse end
+    ?FWR("END Node (no children): ~p~n", [lists:reverse(_Path)]),
+    NodeInfo = maps:fold(fun(K, V, Acc) ->
+                                 Acc ++ K ++ V ++ "~n"
+                         end,
+                         "", NodeInfo0),
+    {NodeInfo, Arrows};
+traverse({_Node, _NoChildren = []}, _Path, Acc) ->
+    ?FWR("Node (no children): ~p~n", [lists:reverse(_Path)]),
+    Acc;
+traverse(TreeNode = {PolicyNode, Children = [Child | Rest]}, Path, _Acc = {NodeInfo0, Arrows0}) ->
+    NodeId = nid(Path),
+    ChildId = nid([length(Children) | Path]),
+    Fmt = "~s --> ~s~n",
+    Args = [NodeId, ChildId],
+    Arrows1 = Arrows0 ++ io_lib:format(Fmt, Args),
+    NodeInfo = store_new_nodes([{NodeId, TreeNode}, {ChildId, Child}], NodeInfo0),
+    Acc2 = traverse(Child, [length(Children) | Path], {NodeInfo, Arrows1}), % traverse vertically
+    traverse({PolicyNode, Rest}, Path, Acc2). % traverse horizontally
+
+store_new_nodes([], NodeInfo) ->
+    NodeInfo;
+store_new_nodes([{Id, Node} | Rest] , NodeInfo0) ->
+    NodeInfo = case maps:is_key(Id, NodeInfo0) of
+                   true ->
+                       NodeInfo0;
+                   false ->
+                       NodeDesc = node_desc(Id, Node),
+                       NodeInfo0#{Id => NodeDesc}
+               end,
+    store_new_nodes(Rest, NodeInfo).
+
+node_desc(Path, {}) ->
+    io_lib:format("[\"(NULL) ~s\"]", [Path]);
+node_desc(Path, #{valid_policy := VP, expected_policy_set := EPS}) ->
+    io_lib:format("[\"(L) ~s\nvp: ~s\neps: ~s\"]", [Path, p(VP), ps(EPS)]);
+node_desc(Path, {#{valid_policy := VP, expected_policy_set := EPS}, _}) ->
+    io_lib:format("[\"(N) ~s\nvp: ~s\neps: ~s\"]", [Path, p(VP), ps(EPS)]).
+
+nid(Path) ->
+    nid(Path, "").
+
+nid([], Acc) ->
+    "N_" ++ lists:reverse(Acc);
+nid([Node], Acc) ->
+    nid([], Acc ++ integer_to_list(Node));
+nid([Node | Rest], Acc) ->
+    nid(Rest, Acc ++ integer_to_list(Node) ++ "-").
+
+ps(ExpectedPolicySet) ->
+    [io_lib:format("~s ", [p(P)]) || P <- ExpectedPolicySet].
+
+p(?ANY_POLICY_OID) ->
+    "anyPolicy";
+p(P) ->
+    P.
-- 
2.35.3

openSUSE Build Service is sponsored by