mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-31 21:38:19 -04:00
module-raop: Add transient HomeKit pairing
This commit is contained in:
parent
75eb403d15
commit
45a0460722
2 changed files with 432 additions and 37 deletions
|
|
@ -64,7 +64,7 @@
|
|||
* #raop.domain = ""
|
||||
* #raop.device = ""
|
||||
* #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"
|
||||
* #audio.channels = 2
|
||||
* #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[] = {
|
||||
{ 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_VERSION, PACKAGE_VERSION },
|
||||
};
|
||||
|
||||
#define SERVICE_TYPE_SINK "_raop._tcp"
|
||||
#define SERVICE_TYPE_RAOP "_raop._tcp"
|
||||
#define SERVICE_TYPE_AP "_airplay._tcp"
|
||||
|
||||
|
||||
struct impl {
|
||||
struct pw_context *context;
|
||||
|
|
@ -122,7 +124,8 @@ struct impl {
|
|||
|
||||
AvahiPoll *avahi_poll;
|
||||
AvahiClient *client;
|
||||
AvahiServiceBrowser *sink_browser;
|
||||
AvahiServiceBrowser *sink_browser_raop;
|
||||
AvahiServiceBrowser *sink_browser_ap;
|
||||
|
||||
struct spa_list tunnel_list;
|
||||
};
|
||||
|
|
@ -182,8 +185,10 @@ static void impl_free(struct impl *impl)
|
|||
spa_list_consume(t, &impl->tunnel_list, link)
|
||||
free_tunnel(t);
|
||||
|
||||
if (impl->sink_browser)
|
||||
avahi_service_browser_free(impl->sink_browser);
|
||||
if (impl->sink_browser_raop)
|
||||
avahi_service_browser_free(impl->sink_browser_raop);
|
||||
if (impl->sink_browser_ap)
|
||||
avahi_service_browser_free(impl->sink_browser_ap);
|
||||
if (impl->client)
|
||||
avahi_client_free(impl->client);
|
||||
if (impl->avahi_poll)
|
||||
|
|
@ -215,7 +220,30 @@ static bool str_in_list(const char *haystack, const char *delimiters, const char
|
|||
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)
|
||||
{
|
||||
if (spa_streq(key, "device")) {
|
||||
|
|
@ -393,6 +421,37 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
|
|||
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);
|
||||
ipv = protocol == AVAHI_PROTO_INET ? 4 : 6;
|
||||
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.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)
|
||||
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_RUNNING:
|
||||
case AVAHI_CLIENT_S_COLLISION:
|
||||
if (impl->sink_browser == NULL)
|
||||
impl->sink_browser = make_browser(impl, SERVICE_TYPE_SINK);
|
||||
if (impl->sink_browser == NULL)
|
||||
if (impl->sink_browser_raop == NULL)
|
||||
impl->sink_browser_raop = make_browser(impl, SERVICE_TYPE_RAOP);
|
||||
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;
|
||||
break;
|
||||
case AVAHI_CLIENT_FAILURE:
|
||||
|
|
@ -513,9 +565,13 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
|
|||
|
||||
SPA_FALLTHROUGH;
|
||||
case AVAHI_CLIENT_CONNECTING:
|
||||
if (impl->sink_browser) {
|
||||
avahi_service_browser_free(impl->sink_browser);
|
||||
impl->sink_browser = NULL;
|
||||
if (impl->sink_browser_raop) {
|
||||
avahi_service_browser_free(impl->sink_browser_raop);
|
||||
impl->sink_browser_raop = NULL;
|
||||
}
|
||||
if (impl->sink_browser_ap) {
|
||||
avahi_service_browser_free(impl->sink_browser_ap);
|
||||
impl->sink_browser_ap = NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
|
@ -26,6 +28,7 @@
|
|||
#include <openssl/engine.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/md5.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
|
@ -44,6 +47,8 @@
|
|||
#include <pipewire/i18n.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
|
||||
*
|
||||
|
|
@ -136,9 +141,16 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
|
|||
#define MD5_DIGEST_LENGTH 16
|
||||
#endif
|
||||
#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_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
|
||||
|
||||
|
|
@ -171,7 +183,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
|
|||
|
||||
static const struct spa_dict_item module_props[] = {
|
||||
{ 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_VERSION, PACKAGE_VERSION },
|
||||
};
|
||||
|
|
@ -183,7 +195,8 @@ enum {
|
|||
enum {
|
||||
CRYPTO_NONE,
|
||||
CRYPTO_RSA,
|
||||
CRYPTO_AUTH_SETUP,
|
||||
CRYPTO_AUTH_SETUP = 4,
|
||||
CRYPTO_PAIR_TRANSIENT = 8,
|
||||
};
|
||||
enum {
|
||||
CODEC_PCM,
|
||||
|
|
@ -232,6 +245,22 @@ struct impl {
|
|||
uint8_t aes_iv[AES_CHUNK_SIZE]; /* Initialization vector for cbc */
|
||||
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;
|
||||
int control_fd;
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
|
|
@ -1409,30 +1722,51 @@ static void rtsp_connected(void *data)
|
|||
{
|
||||
struct impl *impl = data;
|
||||
uint32_t sci[2];
|
||||
uint8_t rac[16];
|
||||
char sac[16*4];
|
||||
int res;
|
||||
|
||||
pw_log_info("connected");
|
||||
|
||||
impl->connected = true;
|
||||
|
||||
if ((res = pw_getrandom(sci, sizeof(sci), 0)) < 0 ||
|
||||
(res = pw_getrandom(rac, sizeof(rac), 0)) < 0) {
|
||||
pw_log_error("error generating random data: %s", spa_strerror(res));
|
||||
// TODO: Do a GET /info first
|
||||
|
||||
if (pw_getrandom(sci, sizeof(sci), 0) < 0) {
|
||||
pw_log_error("error generating random data: %m");
|
||||
return;
|
||||
}
|
||||
|
||||
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_set(impl->headers, "Apple-Challenge", sac);
|
||||
pw_properties_setf(impl->headers, "DACP-ID",
|
||||
"%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);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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");
|
||||
goto error;
|
||||
}
|
||||
|
||||
impl->srp_user = NULL;
|
||||
|
||||
if (args == NULL)
|
||||
args = "";
|
||||
|
||||
|
|
@ -1992,6 +2329,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
|||
impl->encryption = CRYPTO_RSA;
|
||||
else if (spa_streq(str, "auth_setup"))
|
||||
impl->encryption = CRYPTO_AUTH_SETUP;
|
||||
else if (spa_streq(str, "pair_setup"))
|
||||
impl->encryption = CRYPTO_PAIR_TRANSIENT;
|
||||
else {
|
||||
pw_log_error( "can't handle encryption type %s", str);
|
||||
res = -EINVAL;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue