module-raop: Add transient HomeKit pairing

This commit is contained in:
Christian Glombek 2023-07-30 00:54:56 +02:00
parent 75eb403d15
commit 45a0460722
2 changed files with 432 additions and 37 deletions

View file

@ -64,7 +64,7 @@
* #raop.domain = "" * #raop.domain = ""
* #raop.device = "" * #raop.device = ""
* #raop.transport = "udp" | "tcp" * #raop.transport = "udp" | "tcp"
* #raop.encryption.type = "RSA" | "auth_setup" | "none" * #raop.encryption.type = "RSA" | "auth_setup" | "pair_setup" | "none"
* #raop.audio.codec = "PCM" | "ALAC" | "AAC" | "AAC-ELD" * #raop.audio.codec = "PCM" | "ALAC" | "AAC" | "AAC-ELD"
* #audio.channels = 2 * #audio.channels = 2
* #audio.format = "S16" | "S24" | "S32" * #audio.format = "S16" | "S24" | "S32"
@ -105,12 +105,14 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
static const struct spa_dict_item module_props[] = { static const struct spa_dict_item module_props[] = {
{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
{ PW_KEY_MODULE_DESCRIPTION, "Discover remote streams" }, { PW_KEY_MODULE_DESCRIPTION, "Discover remote speakers" },
{ PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_USAGE, MODULE_USAGE },
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
}; };
#define SERVICE_TYPE_SINK "_raop._tcp" #define SERVICE_TYPE_RAOP "_raop._tcp"
#define SERVICE_TYPE_AP "_airplay._tcp"
struct impl { struct impl {
struct pw_context *context; struct pw_context *context;
@ -122,7 +124,8 @@ struct impl {
AvahiPoll *avahi_poll; AvahiPoll *avahi_poll;
AvahiClient *client; AvahiClient *client;
AvahiServiceBrowser *sink_browser; AvahiServiceBrowser *sink_browser_raop;
AvahiServiceBrowser *sink_browser_ap;
struct spa_list tunnel_list; struct spa_list tunnel_list;
}; };
@ -182,8 +185,10 @@ static void impl_free(struct impl *impl)
spa_list_consume(t, &impl->tunnel_list, link) spa_list_consume(t, &impl->tunnel_list, link)
free_tunnel(t); free_tunnel(t);
if (impl->sink_browser) if (impl->sink_browser_raop)
avahi_service_browser_free(impl->sink_browser); avahi_service_browser_free(impl->sink_browser_raop);
if (impl->sink_browser_ap)
avahi_service_browser_free(impl->sink_browser_ap);
if (impl->client) if (impl->client)
avahi_client_free(impl->client); avahi_client_free(impl->client);
if (impl->avahi_poll) if (impl->avahi_poll)
@ -215,7 +220,30 @@ static bool str_in_list(const char *haystack, const char *delimiters, const char
return false; return false;
} }
static void pw_properties_from_avahi_string(const char *key, const char *value, static void pw_properties_from_ap_txt(const char *key, const char *value,
struct pw_properties *props)
{
if (spa_streq(key, "deviceid")) {
pw_properties_set(props, "raop.device", value);
}
else if (spa_streq(key, "features")) {
pw_properties_set(props, "raop.features", value);
}
else if (spa_streq(key, "flags")) {
pw_properties_set(props, "raop.flags", value);
}
else if (spa_streq(key, "pk")) {
pw_properties_set(props, "raop.pk", value);
}
else if (spa_streq(key, "pi")) {
pw_properties_set(props, "raop.pi", value);
}
else if (spa_streq(key, "psi")) {
pw_properties_set(props, "raop.psi", value);
}
}
static void pw_properties_from_raop_txt(const char *key, const char *value,
struct pw_properties *props) struct pw_properties *props)
{ {
if (spa_streq(key, "device")) { if (spa_streq(key, "device")) {
@ -393,6 +421,37 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
goto done; goto done;
} }
pw_log_info("mdns service type: %s", type);
if (strcmp(type, SERVICE_TYPE_AP) == 0) {
for (l = txt; l; l = l->next) {
char *key, *value;
if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
break;
pw_properties_from_ap_txt(key, value, props);
avahi_free(key);
avahi_free(value);
}
// TODO lorbus
// if !feature audio goto done
pw_properties_set(props, "raop.encryption.type", "pair_setup");
pw_properties_set(props, "raop.transport", "udp");
} else {
for (l = txt; l; l = l->next) {
char *key, *value;
if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
break;
pw_properties_from_raop_txt(key, value, props);
avahi_free(key);
avahi_free(value);
}
}
avahi_address_snprint(at, sizeof(at), a); avahi_address_snprint(at, sizeof(at), a);
ipv = protocol == AVAHI_PROTO_INET ? 4 : 6; ipv = protocol == AVAHI_PROTO_INET ? 4 : 6;
pw_properties_setf(props, "raop.ip", "%s", at); pw_properties_setf(props, "raop.ip", "%s", at);
@ -402,17 +461,6 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
pw_properties_setf(props, "raop.hostname", "%s", host_name); pw_properties_setf(props, "raop.hostname", "%s", host_name);
pw_properties_setf(props, "raop.domain", "%s", domain); pw_properties_setf(props, "raop.domain", "%s", domain);
for (l = txt; l; l = l->next) {
char *key, *value;
if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
break;
pw_properties_from_avahi_string(key, value, props);
avahi_free(key);
avahi_free(value);
}
if ((str = pw_properties_get(impl->properties, "raop.latency.ms")) != NULL) if ((str = pw_properties_get(impl->properties, "raop.latency.ms")) != NULL)
pw_properties_set(props, "raop.latency.ms", str); pw_properties_set(props, "raop.latency.ms", str);
@ -502,9 +550,13 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
case AVAHI_CLIENT_S_REGISTERING: case AVAHI_CLIENT_S_REGISTERING:
case AVAHI_CLIENT_S_RUNNING: case AVAHI_CLIENT_S_RUNNING:
case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_COLLISION:
if (impl->sink_browser == NULL) if (impl->sink_browser_raop == NULL)
impl->sink_browser = make_browser(impl, SERVICE_TYPE_SINK); impl->sink_browser_raop = make_browser(impl, SERVICE_TYPE_RAOP);
if (impl->sink_browser == NULL) if (impl->sink_browser_raop == NULL)
goto error;
if (impl->sink_browser_ap == NULL)
impl->sink_browser_ap = make_browser(impl, SERVICE_TYPE_AP);
if (impl->sink_browser_ap == NULL)
goto error; goto error;
break; break;
case AVAHI_CLIENT_FAILURE: case AVAHI_CLIENT_FAILURE:
@ -513,9 +565,13 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
SPA_FALLTHROUGH; SPA_FALLTHROUGH;
case AVAHI_CLIENT_CONNECTING: case AVAHI_CLIENT_CONNECTING:
if (impl->sink_browser) { if (impl->sink_browser_raop) {
avahi_service_browser_free(impl->sink_browser); avahi_service_browser_free(impl->sink_browser_raop);
impl->sink_browser = NULL; impl->sink_browser_raop = NULL;
}
if (impl->sink_browser_ap) {
avahi_service_browser_free(impl->sink_browser_ap);
impl->sink_browser_ap = NULL;
} }
break; break;
default: default:

View file

@ -2,6 +2,8 @@
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <ctype.h>
#include <assert.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <errno.h> #include <errno.h>
@ -26,6 +28,7 @@
#include <openssl/engine.h> #include <openssl/engine.h>
#include <openssl/aes.h> #include <openssl/aes.h>
#include <openssl/md5.h> #include <openssl/md5.h>
#include <openssl/evp.h>
#include "config.h" #include "config.h"
@ -44,6 +47,8 @@
#include <pipewire/i18n.h> #include <pipewire/i18n.h>
#include "module-raop/rtsp-client.h" #include "module-raop/rtsp-client.h"
#include "module-raop/srp.h"
#include "module-raop/tlv.h"
/** \page page_module_raop_sink PipeWire Module: AirPlay Sink /** \page page_module_raop_sink PipeWire Module: AirPlay Sink
* *
@ -136,9 +141,16 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
#define MD5_DIGEST_LENGTH 16 #define MD5_DIGEST_LENGTH 16
#endif #endif
#define MD5_HASH_LENGTH (2*MD5_DIGEST_LENGTH) #define MD5_HASH_LENGTH (2*MD5_DIGEST_LENGTH)
#ifndef SHA512_DIGEST_LENGTH
#define SHA512_DIGEST_LENGTH 64
#endif
#define DEFAULT_USER_AGENT "iTunes/11.0.4 (Windows; N)" #define DEFAULT_USER_AGENT "iTunes/11.0.4 (Windows; N)"
#define DEFAULT_USER_NAME "iTunes" #define DEFAULT_USER_NAME "iTunes"
#define AP_USER_AGENT "AirPlay/381.13"
#define AP_USER_NAME "AirPlay"
#define AP_REQUEST_BUFSIZE 4096
#define AP_SRP_USER_NAME "Pair-Setup"
#define MAX_PORT_RETRY 128 #define MAX_PORT_RETRY 128
@ -171,7 +183,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
static const struct spa_dict_item module_props[] = { static const struct spa_dict_item module_props[] = {
{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
{ PW_KEY_MODULE_DESCRIPTION, "An RAOP audio sink" }, { PW_KEY_MODULE_DESCRIPTION, "An AirPlay audio sink" },
{ PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_USAGE, MODULE_USAGE },
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
}; };
@ -183,7 +195,8 @@ enum {
enum { enum {
CRYPTO_NONE, CRYPTO_NONE,
CRYPTO_RSA, CRYPTO_RSA,
CRYPTO_AUTH_SETUP, CRYPTO_AUTH_SETUP = 4,
CRYPTO_PAIR_TRANSIENT = 8,
}; };
enum { enum {
CODEC_PCM, CODEC_PCM,
@ -232,6 +245,22 @@ struct impl {
uint8_t aes_iv[AES_CHUNK_SIZE]; /* Initialization vector for cbc */ uint8_t aes_iv[AES_CHUNK_SIZE]; /* Initialization vector for cbc */
EVP_CIPHER_CTX *ctx; EVP_CIPHER_CTX *ctx;
struct SRPUser *srp_user;
uint8_t public_key[32];
uint8_t private_key[64];
const uint8_t *pkA;
int pkA_len;
uint8_t *pkB;
uint64_t pkB_len;
const uint8_t *M1;
int M1_len;
uint8_t *M2;
int M2_len;
uint8_t *salt;
uint64_t salt_len;
uint8_t shared_secret[64];
size_t shared_secret_len; // Will be 32 (normal) or 64 (transient)
uint16_t control_port; uint16_t control_port;
int control_fd; int control_fd;
struct spa_source *control_source; struct spa_source *control_source;
@ -1384,6 +1413,290 @@ static int rtsp_do_raop_auth(struct impl *impl, const struct spa_dict *headers)
return rtsp_send(impl, "OPTIONS", NULL, NULL, rtsp_raop_auth_reply); return rtsp_send(impl, "OPTIONS", NULL, NULL, rtsp_raop_auth_reply);
} }
static tlv_values_t * tlv_message_process(const uint8_t *data, size_t data_len)
{
tlv_values_t *response;
tlv_t *error;
int ret;
pw_log_debug("tlv: processing tlv message");
response = tlv_new();
if (!response) {
pw_log_error("tlv: Out of memory");
return NULL;
}
ret = tlv_parse(data, data_len, response);
if (ret < 0) {
pw_log_error("tlv: Could not parse TLV");
goto error;
}
for (tlv_t *t=response->head; t; t=t->next) {
pw_log_info("tlv: parsed type %d value (%zu bytes)", t->type, t->size);
}
error = tlv_get_value(response, AP2_TLVType_Error);
if (error) {
if (error->value[0] == AP2_TLVError_Authentication)
pw_log_error("tlv: Device returned an authentication failure");
else if (error->value[0] == AP2_TLVError_Backoff)
pw_log_error("tlv: Device told us to back off pairing attempts");
else if (error->value[0] == AP2_TLVError_MaxPeers)
pw_log_error("tlv: Max peers trying to connect to device");
else if (error->value[0] == AP2_TLVError_MaxTries)
pw_log_error("tlv: Max pairing attempts reached");
else if (error->value[0] == AP2_TLVError_Unavailable)
pw_log_error("tlv: Device is unavailable at this time");
else
pw_log_error("tlv: Device is busy/returned unknown error");
goto error;
}
return response;
error:
tlv_free(response);
return NULL;
}
static int rtsp_ap2_pair_setup2_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content)
{
struct impl *impl = data;
tlv_values_t *response;
tlv_t *proof;
const uint8_t *session_key;
int session_key_len;
uint32_t content_length;
const char *str;
pw_log_info("pair-setup (req 2) status: %d", status);
if ((str = spa_dict_lookup(headers, "Content-Length")) == NULL) {
pw_log_error("pair-setup (req 2) res: Missing Content-Length");
return -1;
}
if (!spa_atou32(str, &content_length, 0)) {
pw_log_error("pair-setup (req 2) res: Could not read Content-Length");
return -1;
}
pw_log_debug("pair-setup (req 2) res: Content-Length: %s", str);
response = tlv_message_process((const uint8_t *)content->data, (size_t)content_length);
if (!response) {
pw_log_error("pair-setup (req 2) res: Received an error");
return -1;
}
proof = tlv_get_value(response, AP2_TLVType_Proof);
if (!proof || proof->size != SHA512_DIGEST_LENGTH) {
pw_log_error("pair-setup (req 2) res: Missing or invalid proof");
goto error;
}
impl->M2_len = proof->size;
impl->M2 = malloc(impl->M2_len);
memcpy(impl->M2, proof->value, impl->M2_len);
// Check M2
srp_user_verify_session(impl->srp_user, (const unsigned char *)impl->M2);
if (!srp_user_is_authenticated(impl->srp_user)) {
pw_log_error("pair-setup (req 2) res: Server authentication failed");
goto error;
}
session_key = srp_user_get_session_key(impl->srp_user, &session_key_len);
if (!session_key) {
pw_log_error("pair-setup (req 2) res: Could not compute session key");
goto error;
}
if (sizeof(impl->shared_secret) < (unsigned long int) session_key_len) {
pw_log_error("pair-setup (req 2) res: invalid session key");
goto error;
}
memcpy(impl->shared_secret, session_key, session_key_len);
impl->shared_secret_len = session_key_len;
pw_log_info("received shared secret");
tlv_free(response);
// TODO
pw_properties_set(impl->headers, "X-Apple-HKP", NULL);
//return rtsp_do_setup(impl);
return 0;
error:
tlv_free(response);
return -1;
}
static int rtsp_do_ap2_pair_setup2(struct impl *impl)
{
tlv_values_t *request;
uint8_t *data;
size_t data_len = AP_REQUEST_BUFSIZE;
const char *auth_username = NULL;
int ret;
// TLV Pairing Message State
uint8_t state = 0x03;
data = malloc(data_len);
request = tlv_new();
// Calculate A
srp_user_start_authentication(impl->srp_user, &auth_username, &impl->pkA, &impl->pkA_len);
// Calculate M1 (client proof)
srp_user_process_challenge(impl->srp_user, impl->salt, impl->salt_len, impl->pkB, impl->pkB_len, &impl->M1, &impl->M1_len);
tlv_add_value(request, AP2_TLVType_State, &state, sizeof(state));
tlv_add_value(request, AP2_TLVType_PublicKey, impl->pkA, impl->pkA_len);
tlv_add_value(request, AP2_TLVType_Proof, impl->M1, impl->M1_len);
ret = tlv_format(request, data, &data_len);
if (ret < 0) {
pw_log_error("pair-setup 2: tlv_format returned an error");
goto error;
}
for (tlv_t *t=request->head; t; t=t->next) {
pw_log_info("pair-setup 2: sending TLV type %d (bytes: %zu)", t->type, t->size);
}
tlv_free(request);
return pw_rtsp_client_url_send(impl->rtsp, "/pair-setup", "POST", &impl->headers->dict,
"application/octet-stream", data, data_len,
rtsp_ap2_pair_setup2_reply, impl);
error:
tlv_free(request);
free(data);
return -1;
}
static int rtsp_ap2_pair_setup1_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content)
{
struct impl *impl = data;
uint32_t content_length;
const char *len_str;
tlv_values_t *response;
tlv_t *pk;
tlv_t *salt;
pw_log_info("pair-setup (req 1) status: %d", status);
if ((len_str = spa_dict_lookup(headers, "Content-Length")) == NULL) {
pw_log_error("pair-setup (req 1) res: Missing Content-Length header");
return -1;
}
if (!spa_atou32(len_str, &content_length, 0)) {
pw_log_error("pair-setup (req 1) res: Could not read Content-Length header");
return -1;
}
pw_log_info("pair-setup (req 1) content len: %d", content_length);
response = tlv_message_process((const uint8_t *)content->data, (size_t)content_length);
if (!response) {
pw_log_error("pair-setup (req 1) res: message process error");
return -1;
}
pk = tlv_get_value(response, AP2_TLVType_PublicKey);
if (!pk) {
pw_log_error("pair-setup (req 1) res: missing public key");
goto error;
}
if (pk->size > (long unsigned int) get_N_len(SRP_NG_3072)) {
pw_log_error("pair-setup (req 1) res: invalid public key");
goto error;
}
salt = tlv_get_value(response, AP2_TLVType_Salt);
if (!salt) {
pw_log_error("pair-setup (req 1) res: missing salt");
goto error;
}
if (salt->size != 16) {
pw_log_error("pair-setup (req 1) res: invalid salt");
goto error;
}
impl->pkB_len = pk->size;
impl->pkB = malloc(impl->pkB_len);
memcpy(impl->pkB, pk->value, impl->pkB_len);
impl->salt_len = salt->size;
impl->salt = malloc(impl->salt_len);
memcpy(impl->salt, salt->value, impl->salt_len);
tlv_free(response);
return rtsp_do_ap2_pair_setup2(impl);
error:
tlv_free(response);
return -1;
}
static int rtsp_do_ap2_pair_setup1(struct impl *impl)
{
tlv_values_t *request;
uint8_t *data;
size_t data_len;
// TLV Pairing Method: Pair Setup
uint8_t method = 0x00;
// TLV Pairing Flags: Transient
uint8_t flags = 0x10;
// TLV Pairing Message State
uint8_t state = 0x01;
char *pin = "3939";
int ret;
data_len = AP_REQUEST_BUFSIZE;
data = malloc(data_len);
request = tlv_new();
impl->srp_user = srp_user_new(SRP_SHA512, SRP_NG_3072, AP_SRP_USER_NAME, (unsigned char *)pin, strlen(pin), 0, 0);
if (!impl->srp_user) {
pw_log_error("pair-setup 1: SRP user creation failed");
goto error;
}
tlv_add_value(request, AP2_TLVType_State, &state, sizeof(state));
tlv_add_value(request, AP2_TLVType_Method, &method, sizeof(method));
tlv_add_value(request, AP2_TLVType_Flags, &flags, sizeof(flags));
ret = tlv_format(request, data, &data_len);
if (ret < 0) {
pw_log_error("pair-setup 1: tlv_format returned an error");
goto error;
}
for (tlv_t *t=request->head; t; t=t->next) {
pw_log_info("pair-setup 1: sending TLV type %d value (%zu bytes)", t->type, t->size);
}
tlv_free(request);
pw_properties_set(impl->headers, "X-Apple-HKP", "4");
return pw_rtsp_client_url_send(impl->rtsp, "/pair-setup", "POST", &impl->headers->dict,
"application/octet-stream", (unsigned char *)data, data_len,
rtsp_ap2_pair_setup1_reply, impl);
error:
tlv_free(request);
free(data);
return -1;
}
static int rtsp_raop_options_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content) static int rtsp_raop_options_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content)
{ {
struct impl *impl = data; struct impl *impl = data;
@ -1409,30 +1722,51 @@ static void rtsp_connected(void *data)
{ {
struct impl *impl = data; struct impl *impl = data;
uint32_t sci[2]; uint32_t sci[2];
uint8_t rac[16];
char sac[16*4];
int res;
pw_log_info("connected"); pw_log_info("connected");
impl->connected = true; impl->connected = true;
if ((res = pw_getrandom(sci, sizeof(sci), 0)) < 0 || // TODO: Do a GET /info first
(res = pw_getrandom(rac, sizeof(rac), 0)) < 0) {
pw_log_error("error generating random data: %s", spa_strerror(res)); if (pw_getrandom(sci, sizeof(sci), 0) < 0) {
pw_log_error("error generating random data: %m");
return; return;
} }
pw_properties_setf(impl->headers, "Client-Instance", pw_properties_setf(impl->headers, "Client-Instance",
"%08x%08x", sci[0], sci[1]); "%08X%08X", sci[0], sci[1]);
base64_encode(rac, sizeof(rac), sac, '\0'); pw_properties_setf(impl->headers, "DACP-ID",
pw_properties_set(impl->headers, "Apple-Challenge", sac); "%08X%08X", sci[0], sci[1]);
pw_properties_set(impl->headers, "User-Agent", DEFAULT_USER_AGENT); switch (impl->encryption) {
case CRYPTO_PAIR_TRANSIENT:
pw_properties_set(impl->headers, "User-Agent", AP_USER_AGENT);
pw_rtsp_client_send(impl->rtsp, "OPTIONS", &impl->headers->dict, rtsp_do_ap2_pair_setup1(impl);
break;
default:
uint8_t rac[16];
char sac[16*4];
if (pw_getrandom(rac, sizeof(rac), 0) < 0) {
pw_log_error("error generating random data: %m");
return;
}
base64_encode(rac, sizeof(rac), sac, '\0');
pw_properties_set(impl->headers, "Apple-Challenge", sac);
pw_properties_set(impl->headers, "User-Agent", DEFAULT_USER_AGENT);
pw_rtsp_client_send(impl->rtsp, "OPTIONS", &impl->headers->dict,
NULL, NULL, rtsp_raop_options_reply, impl); NULL, NULL, rtsp_raop_options_reply, impl);
break;
}
return;
} }
static void connection_cleanup(struct impl *impl) static void connection_cleanup(struct impl *impl)
@ -1888,6 +2222,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
pw_log_error( "can't create cipher context: %m"); pw_log_error( "can't create cipher context: %m");
goto error; goto error;
} }
impl->srp_user = NULL;
if (args == NULL) if (args == NULL)
args = ""; args = "";
@ -1992,6 +2329,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->encryption = CRYPTO_RSA; impl->encryption = CRYPTO_RSA;
else if (spa_streq(str, "auth_setup")) else if (spa_streq(str, "auth_setup"))
impl->encryption = CRYPTO_AUTH_SETUP; impl->encryption = CRYPTO_AUTH_SETUP;
else if (spa_streq(str, "pair_setup"))
impl->encryption = CRYPTO_PAIR_TRANSIENT;
else { else {
pw_log_error( "can't handle encryption type %s", str); pw_log_error( "can't handle encryption type %s", str);
res = -EINVAL; res = -EINVAL;