network-infra-utility 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -0
  3. data/GUIDE.md +212 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +70 -0
  6. data/Rakefile +1 -1
  7. data/bin/dns-query +836 -0
  8. data/bin/geo-doc +135 -0
  9. data/bin/geo-get +1 -1
  10. data/bin/geo-update +429 -0
  11. data/document/ASNum/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +242 -0
  12. data/document/DNSQuery/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +248 -0
  13. data/document/Geo/345/221/275/344/273/244/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +441 -0
  14. data/document/IP/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +297 -0
  15. data/document/MAC/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +296 -0
  16. data/document/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/344/275/277/347/224/250/346/226/271/346/263/225.md +764 -0
  17. data/network-infra-utility.gemspec +4 -2
  18. data/network.rb +3 -1
  19. data/service/geodb/geodb.rb +278 -1
  20. data/service/ssh/README.md +955 -0
  21. data/service/ssh/bin/ssh-client +198 -0
  22. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/345/212/237/350/203/275/351/234/200/346/261/202/346/226/207/346/241/243.md +292 -0
  23. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/257/246/347/273/206/350/256/276/350/256/241/346/226/207/346/241/243.md +1521 -0
  24. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/275/257/344/273/266/350/256/276/350/256/241/346/226/207/346/241/243.md +2493 -0
  25. data/service/ssh/ext/ssh_core/bin/ssh_core.cmd +28 -0
  26. data/service/ssh/ext/ssh_core/config/sys.config +0 -0
  27. data/service/ssh/ext/ssh_core/config/vm.args +0 -0
  28. data/service/ssh/ext/ssh_core/local_deps/jsx/CHECKSUM +1 -0
  29. data/service/ssh/ext/ssh_core/local_deps/jsx/LICENSE +21 -0
  30. data/service/ssh/ext/ssh_core/local_deps/jsx/README.md +696 -0
  31. data/service/ssh/ext/ssh_core/local_deps/jsx/VERSION +1 -0
  32. data/service/ssh/ext/ssh_core/local_deps/jsx/contents.tar.gz +0 -0
  33. data/service/ssh/ext/ssh_core/local_deps/jsx/metadata.config +15 -0
  34. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.config +17 -0
  35. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.lock +1 -0
  36. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.app.src +10 -0
  37. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.erl +506 -0
  38. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.erl +393 -0
  39. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.hrl +18 -0
  40. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_consult.erl +81 -0
  41. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_decoder.erl +1909 -0
  42. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_encoder.erl +116 -0
  43. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_parser.erl +1214 -0
  44. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_json.erl +408 -0
  45. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_term.erl +389 -0
  46. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_verify.erl +121 -0
  47. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx.erl +506 -0
  48. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.erl +393 -0
  49. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.hrl +18 -0
  50. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_consult.erl +81 -0
  51. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_decoder.erl +1909 -0
  52. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_encoder.erl +116 -0
  53. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_parser.erl +1214 -0
  54. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_json.erl +408 -0
  55. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_term.erl +389 -0
  56. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_verify.erl +121 -0
  57. data/service/ssh/ext/ssh_core/rebar.config +24 -0
  58. data/service/ssh/ext/ssh_core/rebar.lock +1 -0
  59. data/service/ssh/ext/ssh_core/src/ssh_auth_engine.erl +156 -0
  60. data/service/ssh/ext/ssh_core/src/ssh_channel_stm.erl +232 -0
  61. data/service/ssh/ext/ssh_core/src/ssh_codec.erl +83 -0
  62. data/service/ssh/ext/ssh_core/src/ssh_conn_sup.erl +48 -0
  63. data/service/ssh/ext/ssh_core/src/ssh_conn_worker.erl +535 -0
  64. data/service/ssh/ext/ssh_core/src/ssh_core.app.src +36 -0
  65. data/service/ssh/ext/ssh_core/src/ssh_core_app.erl +11 -0
  66. data/service/ssh/ext/ssh_core/src/ssh_core_sup.erl +46 -0
  67. data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +117 -0
  68. data/service/ssh/ext/ssh_core/src/ssh_ipc.hrl +80 -0
  69. data/service/ssh/ext/ssh_core/src/ssh_ipc_coalesce.erl +94 -0
  70. data/service/ssh/ext/ssh_core/src/ssh_ipc_gateway.erl +467 -0
  71. data/service/ssh/ext/ssh_core/src/ssh_ipc_proto.erl +95 -0
  72. data/service/ssh/ext/ssh_core/src/ssh_jump_chain.erl +101 -0
  73. data/service/ssh/ext/ssh_core/src/ssh_keepalive_mgr.erl +222 -0
  74. data/service/ssh/ext/ssh_core/src/ssh_known_hosts_proxy.erl +67 -0
  75. data/service/ssh/ext/ssh_core/src/ssh_port_fwd.erl +225 -0
  76. data/service/ssh/ext/ssh_core/src/ssh_sftp_session.erl +250 -0
  77. data/service/ssh/ext/ssh_core/src/ssh_sftp_sup.erl +62 -0
  78. data/service/ssh/ext/ssh_core_rs/Cargo.lock +2345 -0
  79. data/service/ssh/ext/ssh_core_rs/Cargo.toml +30 -0
  80. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs +34 -0
  81. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs.cmd +40 -0
  82. data/service/ssh/ext/ssh_core_rs/src/channel.rs +296 -0
  83. data/service/ssh/ext/ssh_core_rs/src/coalesce.rs +143 -0
  84. data/service/ssh/ext/ssh_core_rs/src/codec.rs +71 -0
  85. data/service/ssh/ext/ssh_core_rs/src/conn.rs +628 -0
  86. data/service/ssh/ext/ssh_core_rs/src/gateway.rs +389 -0
  87. data/service/ssh/ext/ssh_core_rs/src/handler.rs +293 -0
  88. data/service/ssh/ext/ssh_core_rs/src/keepalive.rs +194 -0
  89. data/service/ssh/ext/ssh_core_rs/src/main.rs +351 -0
  90. data/service/ssh/ext/ssh_core_rs/src/portfwd.rs +378 -0
  91. data/service/ssh/ext/ssh_core_rs/src/proto.rs +198 -0
  92. data/service/ssh/ext/ssh_core_rs/src/sftp.rs +294 -0
  93. data/service/ssh/lib/network_infra_utility/ssh/automation/macro_engine.rb +213 -0
  94. data/service/ssh/lib/network_infra_utility/ssh/client.rb +257 -0
  95. data/service/ssh/lib/network_infra_utility/ssh/config/schema.rb +90 -0
  96. data/service/ssh/lib/network_infra_utility/ssh/config/settings.rb +103 -0
  97. data/service/ssh/lib/network_infra_utility/ssh/config/store.rb +90 -0
  98. data/service/ssh/lib/network_infra_utility/ssh/ipc/coalesce.rb +83 -0
  99. data/service/ssh/lib/network_infra_utility/ssh/ipc/errors.rb +36 -0
  100. data/service/ssh/lib/network_infra_utility/ssh/ipc/router.rb +212 -0
  101. data/service/ssh/lib/network_infra_utility/ssh/ipc/transport.rb +81 -0
  102. data/service/ssh/lib/network_infra_utility/ssh/security/host_key.rb +211 -0
  103. data/service/ssh/lib/network_infra_utility/ssh/security/vault.rb +211 -0
  104. data/service/ssh/lib/network_infra_utility/ssh/session/history.rb +56 -0
  105. data/service/ssh/lib/network_infra_utility/ssh/session/manager.rb +92 -0
  106. data/service/ssh/lib/network_infra_utility/ssh/session/session.rb +109 -0
  107. data/service/ssh/lib/network_infra_utility/ssh/session/tree.rb +95 -0
  108. data/service/ssh/lib/network_infra_utility/ssh/terminal/ansi_parser.rb +435 -0
  109. data/service/ssh/lib/network_infra_utility/ssh/terminal/buffer.rb +78 -0
  110. data/service/ssh/lib/network_infra_utility/ssh/terminal/emulator.rb +159 -0
  111. data/service/ssh/lib/network_infra_utility/ssh/terminal/logger.rb +195 -0
  112. data/service/ssh/lib/network_infra_utility/ssh/terminal/screen.rb +212 -0
  113. data/service/ssh/lib/network_infra_utility/ssh/terminal/theme.rb +127 -0
  114. data/service/ssh/lib/network_infra_utility/ssh/version.rb +7 -0
  115. data/service/ssh/lib/network_infra_utility/ssh.rb +44 -0
  116. data/support/basic/as_num.rb +221 -0
  117. data/support/basic/mac_address.rb +281 -0
  118. data/version.rb +1 -1
  119. metadata +142 -1
@@ -0,0 +1,117 @@
1
+ -module(ssh_infra_sup).
2
+ -behaviour(supervisor).
3
+
4
+ -export([start_link/0, generate_endpoint_file/0]).
5
+ -export([init/1]).
6
+
7
+ -include("ssh_ipc.hrl").
8
+
9
+ %% @doc Start the infrastructure supervisor.
10
+ %% Supervises: ssh_known_hosts_proxy, ssh_keepalive_mgr, ssh_ipc_coalesce, ssh_ipc_gateway
11
+ start_link() ->
12
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
13
+
14
+ %% @doc Return the path to the IPC endpoint file for this VM instance.
15
+ %% The file is written by generate_endpoint_and_token/0 during init.
16
+ generate_endpoint_file() ->
17
+ Tmp = case os:getenv("TMPDIR") of
18
+ false ->
19
+ case os:type() of
20
+ {win32, _} -> os:getenv("TEMP");
21
+ _ -> "/tmp"
22
+ end;
23
+ Dir -> Dir
24
+ end,
25
+ UID = case os:type() of
26
+ {win32, _} -> os:getenv("USERNAME");
27
+ _ -> get_uid()
28
+ end,
29
+ filename:join(Tmp, "ssh_core_" ++ UID ++ ".endpoint").
30
+
31
+ %% @doc Cross-version UID retrieval.
32
+ %% os:getuid/0 was introduced in OTP 26; fall back to os:cmd("id -u")
33
+ %% for older OTP versions (or source-compiled musl builds where the BIF
34
+ %% may be absent).
35
+ get_uid() ->
36
+ case erlang:function_exported(os, getuid, 0) of
37
+ true ->
38
+ integer_to_list(os:getuid());
39
+ false ->
40
+ %% OTP < 26 or musl build without getuid BIF
41
+ string:trim(os:cmd("id -u"))
42
+ end.
43
+
44
+ %% @doc one_for_one, intensity 5/60s.
45
+ %% Keeping infra processes separate from conn_sup so a single connection
46
+ %% crash never cascades to gateway/keepalive/known_hosts.
47
+ init([]) ->
48
+ %% Create ETS indices before any workers start
49
+ ets:new(ch_index, [named_table, public, set]),
50
+ ets:new(sftp_index, [named_table, public, set]),
51
+ ets:new(portfwd_index, [named_table, public, set]),
52
+
53
+ {ok, EndpointToken} = generate_endpoint_and_token(),
54
+ Children = [
55
+ #{
56
+ id => ssh_known_hosts_proxy,
57
+ start => {ssh_known_hosts_proxy, start_link, []},
58
+ restart => permanent,
59
+ type => worker
60
+ },
61
+ #{
62
+ id => ssh_ipc_coalesce,
63
+ start => {ssh_ipc_coalesce, start_link, []},
64
+ restart => permanent,
65
+ type => worker
66
+ },
67
+ #{
68
+ id => ssh_keepalive_mgr,
69
+ start => {ssh_keepalive_mgr, start_link, [?KEEPALIVE_DEFAULT_INTERVAL_MS]},
70
+ restart => permanent,
71
+ type => worker
72
+ },
73
+ #{
74
+ id => ssh_ipc_gateway,
75
+ start => {ssh_ipc_gateway, start_link, [EndpointToken]},
76
+ restart => permanent,
77
+ type => worker
78
+ }
79
+ ],
80
+ SupFlags = #{
81
+ strategy => one_for_one,
82
+ intensity => 5,
83
+ period => 60
84
+ },
85
+ {ok, {SupFlags, Children}}.
86
+
87
+ %% @doc Generate IPC auth token, write endpoint file placeholder.
88
+ %% Actual port is assigned later by ssh_ipc_gateway:start_listener().
89
+ generate_endpoint_and_token() ->
90
+ Token = ssh_codec:encode_b64(crypto:strong_rand_bytes(32)),
91
+ TmpFile = generate_endpoint_file(),
92
+ %% Write placeholder: endpoint line will be rewritten by gateway
93
+ ok = file:write_file(TmpFile, <<"\n", Token/binary>>),
94
+ set_file_perms_600(TmpFile),
95
+ {ok, #{token => Token, file => TmpFile}}.
96
+
97
+ build_endpoint() ->
98
+ case os:type() of
99
+ {win32, _} ->
100
+ %% On Windows, write a placeholder; actual port is assigned
101
+ %% by ssh_ipc_gateway:start_listener() after gen_tcp:listen(0).
102
+ {ok, <<"tcp://127.0.0.1:0">>};
103
+ _ ->
104
+ %% Unix/macOS: Unix socket
105
+ Path = <<"/tmp/ssh_core_", (integer_to_binary(os:getpid()))/binary, ".sock">>,
106
+ {ok, <<"unix:", Path/binary>>}
107
+ end.
108
+
109
+ select_free_port() ->
110
+ {ok, Sock} = gen_tcp:listen(0, [{ip, {127, 0, 0, 1}}]),
111
+ {ok, Port} = inet:port(Sock),
112
+ gen_tcp:close(Sock),
113
+ {ok, Port}.
114
+
115
+ set_file_perms_600(File) ->
116
+ _ = file:change_mode(File, 8#600),
117
+ ok.
@@ -0,0 +1,80 @@
1
+ %% ssh_ipc.hrl — Shared records, macros, and types for IPC and core modules.
2
+
3
+ %% ---------- Protocol ----------
4
+ -define(PROTOCOL_VER, <<"1.0">>).
5
+ -define(MSG_DELIMITER, <<"\n">>).
6
+ -define(MAX_MSG_SIZE, 2 * 1024 * 1024). % 2 MB
7
+
8
+ %% ---------- Timeouts (ms) ----------
9
+ -define(CONNECT_TIMEOUT_MS, 60_000).
10
+ -define(RPC_DEFAULT_TIMEOUT_MS, 30_000).
11
+ -define(HELLO_ID, 1).
12
+ -define(HOSTKEY_RESOLVE_TIMEOUT_MS, 30_000).
13
+
14
+ %% ---------- Coalesce ----------
15
+ -define(COALESCE_TICK_MS, 8).
16
+ -define(COALESCE_WATERMARK_BYTES, 16 * 1024).
17
+ -define(FLOW_PAUSE_THRESHOLD, 512 * 1024).
18
+ -define(FLOW_RESUME_THRESHOLD, 128 * 1024).
19
+
20
+ %% ---------- Keepalive ----------
21
+ -define(KEEPALIVE_DEFAULT_INTERVAL_MS, 30_000).
22
+ -define(KEEPALIVE_DEFAULT_MAX_FAIL, 3).
23
+
24
+ %% ---------- Reconnect ----------
25
+ -define(RECONNECT_BASE_MS, 5_000).
26
+ -define(RECONNECT_MAX_MS, 60_000).
27
+ -define(RECONNECT_MAX_ATTEMPTS, 5).
28
+
29
+ %% ---------- IPC client record ----------
30
+ -record(client, {
31
+ socket :: gen_tcp:socket() | port(),
32
+ authed = false :: boolean(),
33
+ pending = #{} :: #{integer() => {erlang:timestamp(), term()}},
34
+ send_q = queue:new() :: queue:queue(binary()),
35
+ flow_blocked = false :: boolean()
36
+ }).
37
+
38
+ %% ---------- Connection record (conn_worker) ----------
39
+ -record(conn, {
40
+ id :: binary(),
41
+ spec :: map(),
42
+ ssh_ref :: reference() | undefined,
43
+ channels = #{} :: #{binary() => pid()},
44
+ next_ch_seq = 1 :: pos_integer(),
45
+ reconnect_count = 0 :: non_neg_integer(),
46
+ options :: [proplists:property()],
47
+ fail_reason :: term() | undefined,
48
+ proxy_sock_fun :: function() | undefined
49
+ }).
50
+
51
+ %% ---------- Channel record (channel_stm) ----------
52
+ -record(ch, {
53
+ id :: binary(),
54
+ conn_id :: binary(),
55
+ ssh_ref :: reference(),
56
+ ssh_chan_id :: integer(),
57
+ type :: shell | exec | subsystem,
58
+ term_type :: binary() | undefined,
59
+ cols :: pos_integer(),
60
+ rows :: pos_integer(),
61
+ command :: binary() | undefined,
62
+ coalesce_ref :: pid() | undefined
63
+ }).
64
+
65
+ %% ---------- SFTP session record ----------
66
+ -record(sftp, {
67
+ id :: binary(),
68
+ conn_id :: binary(),
69
+ ssh_ref :: reference(),
70
+ sftp_pid :: pid() | undefined
71
+ }).
72
+
73
+ %% ---------- Gateway state ----------
74
+ -record(gw_state, {
75
+ listen_sock,
76
+ clients = #{} :: #{reference() => #client{}},
77
+ auth_token :: binary(),
78
+ routes :: #{binary() => {module(), atom()}},
79
+ coalesce_ref :: pid() | undefined
80
+ }).
@@ -0,0 +1,94 @@
1
+ -module(ssh_ipc_coalesce).
2
+ -behaviour(gen_server).
3
+
4
+ %% High-frequency push coalescer with backpressure watermark.
5
+ %% Receives per-channel data via enqueue/2 and flushes batched
6
+ %% channel.data.batch pushes every COALESCE_TICK_MS or when
7
+ %% the buffer reaches COALESCE_WATERMARK_BYTES.
8
+
9
+ -export([start_link/0, enqueue/3, pause/1, resume/1]).
10
+ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
11
+
12
+ -include("ssh_ipc.hrl").
13
+
14
+ -record(st, {
15
+ buffer = #{} :: #{binary() => [binary()]}, % channel_id => [data]
16
+ buffer_size = 0 :: non_neg_integer(),
17
+ tick_ref :: reference() | undefined
18
+ }).
19
+
20
+ %% ---------- Public API ----------
21
+
22
+ %% @doc Start the coalesce gen_server.
23
+ start_link() ->
24
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
25
+
26
+ %% @doc Enqueue data for a channel. Called by ssh_channel_stm.
27
+ enqueue(CoalesceRef, ChannelId, Data) ->
28
+ gen_server:cast(CoalesceRef, {enqueue, ChannelId, Data}).
29
+
30
+ %% @doc Signal flow pause for a channel (backpressure).
31
+ pause(ChannelId) ->
32
+ gen_server:cast(?MODULE, {pause, ChannelId}).
33
+
34
+ %% @doc Signal flow resume for a channel.
35
+ resume(ChannelId) ->
36
+ gen_server:cast(?MODULE, {resume, ChannelId}).
37
+
38
+ %% ---------- Callbacks ----------
39
+
40
+ init([]) ->
41
+ TickRef = schedule_tick(),
42
+ {ok, #st{tick_ref = TickRef}}.
43
+
44
+ handle_call(_Req, _From, State) ->
45
+ {reply, {error, not_implemented}, State}.
46
+
47
+ handle_cast({enqueue, ChannelId, Data}, #st{buffer = Buf, buffer_size = Sz} = S) ->
48
+ NewBuf = maps:update_with(ChannelId, fun(Items) -> Items ++ [Data] end, [Data], Buf),
49
+ NewSz = Sz + byte_size(Data),
50
+ S1 = S#st{buffer = NewBuf, buffer_size = NewSz},
51
+ %% Flush immediately if watermark reached
52
+ S2 = case NewSz >= ?COALESCE_WATERMARK_BYTES of
53
+ true -> do_flush(S1);
54
+ false -> S1
55
+ end,
56
+ {noreply, S2};
57
+
58
+ handle_cast({pause, ChannelId}, S) ->
59
+ ssh_ipc_gateway:push_event(<<"channel.flow.pause">>, #{<<"channel_id">> => ChannelId}),
60
+ {noreply, S};
61
+
62
+ handle_cast({resume, ChannelId}, S) ->
63
+ ssh_ipc_gateway:push_event(<<"channel.flow.resume">>, #{<<"channel_id">> => ChannelId}),
64
+ {noreply, S};
65
+
66
+ handle_cast(_Msg, State) ->
67
+ {noreply, State}.
68
+
69
+ handle_info(tick, S) ->
70
+ S1 = do_flush(S),
71
+ S2 = S1#st{tick_ref = schedule_tick()},
72
+ {noreply, S2};
73
+
74
+ handle_info(_Info, State) ->
75
+ {noreply, State}.
76
+
77
+ terminate(_Reason, _State) ->
78
+ ok.
79
+
80
+ %% ---------- Internal ----------
81
+
82
+ schedule_tick() ->
83
+ erlang:send_after(?COALESCE_TICK_MS, self(), tick).
84
+
85
+ %% @doc Flush the coalesce buffer as a channel.data.batch push.
86
+ do_flush(#st{buffer = Buf} = S) when map_size(Buf) =:= 0 ->
87
+ S;
88
+ do_flush(#st{buffer = Buf} = S) ->
89
+ Items = maps:fold(fun(ChId, Datas, Acc) ->
90
+ B64 = ssh_codec:encode_b64(iolist_to_binary(Datas)),
91
+ [#{<<"id">> => ChId, <<"data">> => B64} | Acc]
92
+ end, [], Buf),
93
+ ssh_ipc_gateway:push_batch(#{<<"items">> => Items}),
94
+ S#st{buffer = #{}, buffer_size = 0}.