From 08101a69d8b06f176c6f5e975ddfc1a562864bd2 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 19 Jun 2019 17:52:55 -0400
Subject: [PATCH 16/20] fix: dbus: new dict based APIs for services
Since we can't change the dbus APIs, we need new ones that are more
flexible. This adds a few for manipulating services using a dictionary
of key,value pairs. All new code should use these new APIs. The old APIs
have been marked deprecated.
Fixes: 1fc208bf9317 ("feat: service includes")
(cherry picked from commit bbadd3943dabcc11e864223503a46144b0c03007)
---
doc/xml/firewalld.dbus.xml | 92 +++++++++++++++++++--------
src/firewall/core/io/service.py | 19 +++---
src/firewall/server/config.py | 18 +++++-
src/firewall/server/config_service.py | 24 ++++++-
src/firewall/server/firewalld.py | 13 +++-
5 files changed, 123 insertions(+), 43 deletions(-)
diff --git a/doc/xml/firewalld.dbus.xml b/doc/xml/firewalld.dbus.xml
index cb4e1eac0fb9..4a81e8e61858 100644
--- a/doc/xml/firewalld.dbus.xml
+++ b/doc/xml/firewalld.dbus.xml
@@ -241,13 +241,22 @@
</para>
</listitem>
</varlistentry>
- <varlistentry id="FirewallD1.Methods.getServiceSettings">
+ <varlistentry id="FirewallD1.Methods.getServiceSettings">
+ <annotation name="org.freedesktop.DBus.Deprecated" />
<term><methodname>getServiceSettings</methodname>(s: <parameter>service</parameter>) → (sssa(ss)asa{ss}asa(ss))</term>
+ <listitem>
+ <para>
+ This function is deprecated, use <link linkend="FirewallD1.Methods.getServiceSettings2">org.fedoraproject.FirewallD1.Methods.getServiceSettings2</link> instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="FirewallD1.Methods.getServiceSettings2">
+ <term><methodname>getServiceSettings2</methodname>(s: <parameter>service</parameter>) → s{sv}</term>
<listitem>
<para>
Return runtime settings of given <replaceable>service</replaceable>.
- For getting permanent settings see <link linkend="FirewallD1.config.service.Methods.getSettings">org.fedoraproject.FirewallD1.config.service.Methods.getSettings</link>.
- Settings are in format: <parameter>version</parameter>, <parameter>name</parameter>, <parameter>description</parameter>, array of <parameter>ports</parameter> (port, protocol), array of <parameter>module names</parameter>, dictionary of <parameter>destinations</parameter>, array of <parameter>protocols</parameter>, array of <parameter>source-ports</parameter> (port, protocol).
+ For getting permanent settings see <link linkend="FirewallD1.config.service.Methods.getSettings2">org.fedoraproject.FirewallD1.config.service.Methods.getSettings2</link>.
+ Settings are a dictionary indexed by keywords. For the type of each value see below. If the value is empty it may be ommitted.
</para>
<para>
<variablelist>
@@ -258,12 +267,13 @@
<varlistentry><term><parameter>module names (as)</parameter>: array of kernel netfilter helpers, see <literal>module</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
<varlistentry><term><parameter>destinations (a{ss})</parameter>: dictionary of {IP family : IP address} where 'IP family' key can be either 'ipv4' or 'ipv6'. See <literal>destination</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
<varlistentry><term><parameter>protocols (as)</parameter>: array of protocols, see <literal>protocol</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
- <varlistentry><term><parameter>source-ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>source-port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>source_ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>source-port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>includes (as)</parameter>: array of service includes, see <literal>include</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
</variablelist>
</para>
- <para>
- Possible errors: INVALID_SERVICE
- </para>
+ <para>
+ Possible errors: INVALID_SERVICE
+ </para>
</listitem>
</varlistentry>
<varlistentry id="FirewallD1.Methods.getZoneSettings">
@@ -2293,12 +2303,20 @@
</listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.Methods.addService">
+ <annotation name="org.freedesktop.DBus.Deprecated" />
<term><methodname>addService</methodname>(s: service, (sssa(ss)asa{ss}asa(ss)): settings) → o</term>
<listitem>
<para>
- Add <replaceable>service</replaceable> with given <replaceable>settings</replaceable> into permanent configuration.
- Settings are in format: <parameter>version</parameter>, <parameter>name</parameter>, <parameter>description</parameter>, array of <parameter>ports</parameter> (port, protocol), array of <parameter>module names</parameter>, dictionary of <parameter>destinations</parameter>, array of <parameter>protocols</parameter> and array of <parameter>source-ports</parameter> (port, protocol).
- Returns object path of the new icmp type.
+ This function is deprecated, use <link linkend="FirewallD1.config.Methods.addService2">org.fedoraproject.FirewallD1.config.Methods.addService2</link> instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="FirewallD1.config.Methods.addService2">
+ <term><methodname>addService2</methodname>s: service, a{sv}: settings) → o</term>
+ <listitem>
+ <para>
+ Add <replaceable>service</replaceable> with given <replaceable>settings</replaceable> into permanent configuration.
+ Settings are a dictionary indexed by keywords. For the type of each value see below. To zero a value pass an empty string or list.
</para>
<para>
<variablelist>
@@ -2308,13 +2326,14 @@
<varlistentry><term><parameter>ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
<varlistentry><term><parameter>module names (as)</parameter>: array of kernel netfilter helpers, see <literal>module</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
<varlistentry><term><parameter>destinations (a{ss})</parameter>: dictionary of {IP family : IP address} where 'IP family' key can be either 'ipv4' or 'ipv6'. See <literal>destination</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
- <varlistentry><term><parameter>protocols (as)</parameter>: array of protocols. See <literal>protocol</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
- <varlistentry><term><parameter>source-ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>source-port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>protocols (as)</parameter>: array of protocols, see <literal>protocol</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>source_ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>source-port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>includes (as)</parameter>: array of service includes, see <literal>include</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
</variablelist>
</para>
- <para>
- Possible errors: NAME_CONFLICT, INVALID_NAME, INVALID_TYPE
- </para>
+ <para>
+ Possible errors: NAME_CONFLICT, INVALID_NAME, INVALID_TYPE
+ </para>
</listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.Methods.addZone">
@@ -4500,12 +4519,21 @@
</listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.service.Methods.getSettings">
+ <annotation name="org.freedesktop.DBus.Deprecated" />
<term><methodname>getSettings</methodname>() → (sssa(ss)asa{ss}asa(ss))</term>
<listitem>
<para>
- Return permanent settings of a <replaceable>service</replaceable>.
- For getting runtime settings see <link linkend="FirewallD1.Methods.getServiceSettings">org.fedoraproject.FirewallD1.Methods.getServiceSettings</link>.
- Settings are in format: <parameter>version</parameter>, <parameter>name</parameter>, <parameter>description</parameter>, array of <parameter>ports</parameter> (port, protocol), array of <parameter>module names</parameter>, dictionary of <parameter>destinations</parameter>, array of <parameter>protocols</parameter> and array of <parameter>source-ports</parameter> (port, protocol).
+ This function is deprecated, use <link linkend="FirewallD1.config.service.Methods.getSettings2">org.fedoraproject.FirewallD1.config.service.Methods.getSettings2</link> instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="FirewallD1.config.service.Methods.getSettings2">
+ <term><methodname>getSettings2</methodname>(s: <parameter>service</parameter>) → s{sv}</term>
+ <listitem>
+ <para>
+ Return runtime settings of given <replaceable>service</replaceable>.
+ For getting runtime settings see <link linkend="FirewallD1.Methods.getServiceSettings2">org.fedoraproject.FirewallD1.Methods.getServiceSettings2</link>.
+ Settings are a dictionary indexed by keywords. For the type of each value see below. If the value is empty it may be ommitted.
</para>
<para>
<variablelist>
@@ -4515,8 +4543,9 @@
<varlistentry><term><parameter>ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
<varlistentry><term><parameter>module names (as)</parameter>: array of kernel netfilter helpers, see <literal>module</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
<varlistentry><term><parameter>destinations (a{ss})</parameter>: dictionary of {IP family : IP address} where 'IP family' key can be either 'ipv4' or 'ipv6'. See <literal>destination</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
- <varlistentry><term><parameter>protocols (as)</parameter>: array of protocols. See <literal>protocol</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
- <varlistentry><term><parameter>source-ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>source-port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>protocols (as)</parameter>: array of protocols, see <literal>protocol</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>source_ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>source-port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>includes (as)</parameter>: array of service includes, see <literal>include</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
</variablelist>
</para>
</listitem>
@@ -4774,11 +4803,20 @@
</listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.service.Methods.update">
+ <annotation name="org.freedesktop.DBus.Deprecated" />
<term><methodname>update</methodname>((sssa(ss)asa{ss}asa(ss)): settings) → Nothing</term>
<listitem>
<para>
- Update settings of service to <replaceable>settings</replaceable>.
- Settings are in format: <parameter>version</parameter>, <parameter>name</parameter>, <parameter>description</parameter>, array of <parameter>ports</parameter> (port, protocol), array of <parameter>module names</parameter>, dictionary of <parameter>destinations</parameter>, array of <parameter>protocols</parameter> and array of <parameter>source-ports</parameter> (port, protocol).
+ This function is deprecated, use <link linkend="FirewallD1.config.service.Methods.update2">org.fedoraproject.FirewallD1.config.service.Methods.update2</link> instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="FirewallD1.config.service.Methods.update2">
+ <term><methodname>update2</methodname>a{sv}: settings) → Nothing</term>
+ <listitem>
+ <para>
+ Update settings of service to <replaceable>settings</replaceable>.
+ Settings are a dictionary indexed by keywords. For the type of each value see below. To zero a value pass an empty string or list.
</para>
<para>
<variablelist>
@@ -4788,12 +4826,14 @@
<varlistentry><term><parameter>ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
<varlistentry><term><parameter>module names (as)</parameter>: array of kernel netfilter helpers, see <literal>module</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
<varlistentry><term><parameter>destinations (a{ss})</parameter>: dictionary of {IP family : IP address} where 'IP family' key can be either 'ipv4' or 'ipv6'. See <literal>destination</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
- <varlistentry><term><parameter>protocols (as)</parameter>: array of protocols. See <literal>protocol</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>protocols (as)</parameter>: array of protocols, see <literal>protocol</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>source_ports (a(ss))</parameter>: array of port and protocol pairs. See <literal>source-port</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
+ <varlistentry><term><parameter>includes (as)</parameter>: array of service includes, see <literal>include</literal> tag in <citerefentry><refentrytitle>firewalld.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</term></varlistentry>
</variablelist>
</para>
- <para>
- Possible errors: INVALID_TYPE
- </para>
+ <para>
+ Possible errors: INVALID_TYPE
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/firewall/core/io/service.py b/src/firewall/core/io/service.py
index 44dc0ff8a9b0..8236d3078fbe 100644
--- a/src/firewall/core/io/service.py
+++ b/src/firewall/core/io/service.py
@@ -39,17 +39,16 @@ from firewall.errors import FirewallError
class Service(IO_Object):
IMPORT_EXPORT_STRUCTURE = (
- ( "version", "" ), # s
- ( "short", "" ), # s
- ( "description", "" ), # s
- ( "ports", [ ( "", "" ), ], ), # a(ss)
- ( "modules", [ "", ], ), # as
- ( "destination", { "": "", }, ), # a{ss}
- ( "protocols", [ "", ], ), # as
- ( "source_ports", [ ( "", "" ), ], ), # a(ss)
- ( "includes", [ "" ], ), # as
+ ( "version", "" ),
+ ( "short", "" ),
+ ( "description", "" ),
+ ( "ports", [ ( "", "" ), ], ),
+ ( "modules", [ "", ], ),
+ ( "destination", { "": "", }, ),
+ ( "protocols", [ "", ], ),
+ ( "source_ports", [ ( "", "" ), ], ),
+ ( "includes", [ "" ], ),
)
- DBUS_SIGNATURE = '(sssa(ss)asa{ss}asa(ss))'
ADDITIONAL_ALNUM_CHARS = [ "_", "-" ]
PARSER_REQUIRED_ELEMENT_ATTRS = {
"short": None,
diff --git a/src/firewall/server/config.py b/src/firewall/server/config.py
index 971dc7d4a14a..e03c4984e058 100644
--- a/src/firewall/server/config.py
+++ b/src/firewall/server/config.py
@@ -41,7 +41,6 @@ from firewall.server.config_zone import FirewallDConfigZone
from firewall.server.config_ipset import FirewallDConfigIPSet
from firewall.server.config_helper import FirewallDConfigHelper
from firewall.core.io.zone import Zone
-from firewall.core.io.service import Service
from firewall.core.io.icmptype import IcmpType
from firewall.core.io.ipset import IPSet
from firewall.core.io.helper import Helper
@@ -1065,7 +1064,7 @@ class FirewallDConfig(slip.dbus.service.Object):
raise FirewallError(errors.INVALID_SERVICE, service)
@dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
- in_signature='s'+Service.DBUS_SIGNATURE,
+ in_signature='s(sssa(ss)asa{ss}asa(ss))',
out_signature='o')
@dbus_handle_exceptions
def addService(self, service, settings, sender=None):
@@ -1079,6 +1078,21 @@ class FirewallDConfig(slip.dbus.service.Object):
config_service = self._addService(obj)
return config_service
+ @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
+ in_signature='sa{sv}',
+ out_signature='o')
+ @dbus_handle_exceptions
+ def addService2(self, service, settings, sender=None):
+ """add service with given name and settings
+ """
+ service = dbus_to_python(service, str)
+ settings = dbus_to_python(settings)
+ log.debug1("config.addService2('%s')", service)
+ self.accessCheck(sender)
+ obj = self.config.new_service_dict(service, settings)
+ config_service = self._addService(obj)
+ return config_service
+
@dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG, signature='s')
@dbus_handle_exceptions
def ServiceAdded(self, service):
diff --git a/src/firewall/server/config_service.py b/src/firewall/server/config_service.py
index 05ded1c78da7..3236b3aee135 100644
--- a/src/firewall/server/config_service.py
+++ b/src/firewall/server/config_service.py
@@ -32,7 +32,6 @@ from firewall import config
from firewall.dbus_utils import dbus_to_python, \
dbus_introspection_prepare_properties, \
dbus_introspection_add_properties
-from firewall.core.io.service import Service
from firewall.core.logger import log
from firewall.server.decorators import handle_exceptions, \
dbus_handle_exceptions, dbus_service_method
@@ -173,7 +172,7 @@ class FirewallDConfigService(slip.dbus.service.Object):
# S E T T I N G S
@dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_SERVICE,
- out_signature=Service.DBUS_SIGNATURE)
+ out_signature='(sssa(ss)asa{ss}asa(ss))')
@dbus_handle_exceptions
def getSettings(self, sender=None): # pylint: disable=W0613
"""get settings for service
@@ -182,7 +181,16 @@ class FirewallDConfigService(slip.dbus.service.Object):
return self.config.get_service_config(self.obj)
@dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_SERVICE,
- in_signature=Service.DBUS_SIGNATURE)
+ out_signature='a{sv}')
+ @dbus_handle_exceptions
+ def getSettings2(self, sender=None):
+ """get settings for service
+ """
+ log.debug1("%s.getSettings2()", self._log_prefix)
+ return self.config.get_service_config_dict(self.obj)
+
+ @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_SERVICE,
+ in_signature='(sssa(ss)asa{ss}asa(ss))')
@dbus_handle_exceptions
def update(self, settings, sender=None):
"""update settings for service
@@ -193,6 +201,16 @@ class FirewallDConfigService(slip.dbus.service.Object):
self.obj = self.config.set_service_config(self.obj, settings)
self.Updated(self.obj.name)
+ @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_SERVICE,
+ in_signature='a{sv}')
+ @dbus_handle_exceptions
+ def update2(self, settings, sender=None):
+ settings = dbus_to_python(settings)
+ log.debug1("%s.update2('...')", self._log_prefix)
+ self.parent.accessCheck(sender)
+ self.obj = self.config.set_service_config_dict(self.obj, settings)
+ self.Updated(self.obj.name)
+
@dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_SERVICE)
@dbus_handle_exceptions
def loadDefaults(self, sender=None):
diff --git a/src/firewall/server/firewalld.py b/src/firewall/server/firewalld.py
index 233160b64b18..06c2834c602e 100644
--- a/src/firewall/server/firewalld.py
+++ b/src/firewall/server/firewalld.py
@@ -49,7 +49,6 @@ from firewall.dbus_utils import dbus_to_python, \
from firewall.core.io.functions import check_config
from firewall.core.io.zone import Zone
from firewall.core.io.ipset import IPSet
-from firewall.core.io.service import Service
from firewall.core.io.icmptype import IcmpType
from firewall.core.io.helper import Helper
from firewall.core.fw_nm import nm_get_bus_name, nm_get_connection_of_interface, \
@@ -916,7 +915,7 @@ class FirewallD(slip.dbus.service.Object):
@slip.dbus.polkit.require_auth(config.dbus.PK_ACTION_CONFIG_INFO)
@dbus_service_method(config.dbus.DBUS_INTERFACE, in_signature='s',
- out_signature=Service.DBUS_SIGNATURE)
+ out_signature='(sssa(ss)asa{ss}asa(ss))')
@dbus_handle_exceptions
def getServiceSettings(self, service, sender=None): # pylint: disable=W0613
# returns service settings for service
@@ -934,6 +933,16 @@ class FirewallD(slip.dbus.service.Object):
conf_list.append(conf_dict[obj.IMPORT_EXPORT_STRUCTURE[i][0]])
return tuple(conf_list)
+ @slip.dbus.polkit.require_auth(config.dbus.PK_ACTION_CONFIG_INFO)
+ @dbus_service_method(config.dbus.DBUS_INTERFACE, in_signature='s',
+ out_signature='a{sv}')
+ @dbus_handle_exceptions
+ def getServiceSettings2(self, service, sender=None): # pylint: disable=W0613
+ service = dbus_to_python(service, str)
+ log.debug1("getServiceSettings2(%s)", service)
+ obj = self.fw.service.get_service(service)
+ return obj.export_config()
+
@slip.dbus.polkit.require_auth(config.dbus.PK_ACTION_INFO)
@dbus_service_method(config.dbus.DBUS_INTERFACE, in_signature='',
out_signature='as')
--
2.20.1