Skip to content

Commit

Permalink
[spotify] Import version 0.4 of librespot-c and remove password-based…
Browse files Browse the repository at this point in the history
… login

Experimental version to test new protocol
  • Loading branch information
ejurgensen committed Feb 4, 2025
1 parent 591a0b6 commit 679e407
Show file tree
Hide file tree
Showing 73 changed files with 10,867 additions and 1,785 deletions.
21 changes: 18 additions & 3 deletions src/inputs/librespot-c/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,31 @@ PROTO_SRC = \
src/proto/mercury.pb-c.c src/proto/mercury.pb-c.h \
src/proto/metadata.pb-c.c src/proto/metadata.pb-c.h

HTTP_PROTO_SRC = \
src/proto/connectivity.pb-c.c src/proto/connectivity.pb-c.h \
src/proto/clienttoken.pb-c.c src/proto/clienttoken.pb-c.h \
src/proto/login5_user_info.pb-c.h src/proto/login5_user_info.pb-c.c \
src/proto/login5.pb-c.h src/proto/login5.pb-c.c \
src/proto/login5_identifiers.pb-c.h src/proto/login5_identifiers.pb-c.c \
src/proto/login5_credentials.pb-c.h src/proto/login5_credentials.pb-c.c \
src/proto/login5_client_info.pb-c.h src/proto/login5_client_info.pb-c.c \
src/proto/login5_challenges_hashcash.pb-c.h src/proto/login5_challenges_hashcash.pb-c.c \
src/proto/login5_challenges_code.pb-c.h src/proto/login5_challenges_code.pb-c.c \
src/proto/google_duration.pb-c.h src/proto/google_duration.pb-c.c \
src/proto/storage_resolve.pb-c.h src/proto/storage_resolve.pb-c.c

CORE_SRC = \
src/librespot-c.c src/connection.c src/channel.c src/crypto.c src/commands.c
src/librespot-c.c src/connection.c src/channel.c src/crypto.c src/commands.c \
src/http.c

librespot_c_a_SOURCES = \
$(CORE_SRC) \
$(SHANNON_SRC) \
$(PROTO_SRC)
$(PROTO_SRC) \
$(HTTP_PROTO_SRC)

noinst_HEADERS = \
librespot-c.h src/librespot-c-internal.h src/connection.h \
src/channel.h src/crypto.h src/commands.h
src/channel.h src/crypto.h src/commands.h src/http.h

EXTRA_DIST = README.md LICENSE
5 changes: 5 additions & 0 deletions src/inputs/librespot-c/configure.ac
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
AC_INIT([librespot-c], [0.1])
AC_CONFIG_AUX_DIR([.])
AM_INIT_AUTOMAKE([foreign subdir-objects])
AM_SILENT_RULES([yes])

AC_PROG_CC
AM_PROG_AR
AC_PROG_RANLIB

AM_CPPFLAGS="-Wall"
AC_SUBST([AM_CPPFLAGS])

AC_CHECK_HEADERS_ONCE([sys/utsname.h])

AC_CHECK_HEADERS([endian.h sys/endian.h libkern/OSByteOrder.h], [found_endian_headers=yes; break;])
Expand Down
17 changes: 11 additions & 6 deletions src/inputs/librespot-c/librespot-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <pthread.h>

#define LIBRESPOT_C_VERSION_MAJOR 0
#define LIBRESPOT_C_VERSION_MINOR 2
#define LIBRESPOT_C_VERSION_MINOR 4


struct sp_session;
Expand Down Expand Up @@ -37,18 +37,21 @@ struct sp_metadata
size_t file_len;
};

// How to identify towards Spotify. The device_id can be set to an actual value
// identifying the client, but the rest are unfortunately best left as zeroes,
// which will make librespot-c use defaults that spoof whitelisted clients.
struct sp_sysinfo
{
char client_name[16];
char client_id[33];
char client_version[16];
char client_build_id[16];
char device_id[41]; // librespot gives a 20 byte id (so 40 char hex + 1 zero term)
};

struct sp_callbacks
{
// Bring your own https client and tcp connector
int (*https_get)(char **body, const char *url);
// Bring your own tcp connector
int (*tcp_connect)(const char *address, unsigned short port);
void (*tcp_disconnect)(int fd);

Expand All @@ -60,10 +63,9 @@ struct sp_callbacks
void (*logmsg)(const char *fmt, ...);
};



// Deprecated, use login_token and login_stored_cred instead
struct sp_session *
librespotc_login_password(const char *username, const char *password);
librespotc_login_password(const char *username, const char *password) __attribute__ ((deprecated));

struct sp_session *
librespotc_login_stored_cred(const char *username, uint8_t *stored_cred, size_t stored_cred_len);
Expand All @@ -74,6 +76,9 @@ librespotc_login_token(const char *username, const char *token);
int
librespotc_logout(struct sp_session *session);

int
librespotc_legacy_set(struct sp_session *session, int use_legacy);

int
librespotc_bitrate_set(struct sp_session *session, enum sp_bitrates bitrate);

Expand Down
63 changes: 42 additions & 21 deletions src/inputs/librespot-c/src/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ channel_get(uint32_t channel_id, struct sp_session *session)
void
channel_free(struct sp_channel *channel)
{
int i;

if (!channel || channel->state == SP_CHANNEL_STATE_UNALLOCATED)
return;

Expand All @@ -82,6 +84,9 @@ channel_free(struct sp_channel *channel)

free(channel->file.path);

for (i = 0; i < ARRAY_SIZE(channel->file.cdnurl); i++)
free(channel->file.cdnurl[i]);

memset(channel, 0, sizeof(struct sp_channel));

channel->audio_fd[0] = -1;
Expand Down Expand Up @@ -208,7 +213,8 @@ channel_seek_internal(struct sp_channel *channel, size_t pos, bool do_flush)
channel->seek_pos = pos;

// If seek + header isn't word aligned we will get up to 3 bytes before the
// actual seek position. We will remove those when they are received.
// actual seek position with the legacy protocol. We will remove those when
// they are received.
channel->seek_align = (pos + SP_OGG_HEADER_LEN) % 4;

seek_words = (pos + SP_OGG_HEADER_LEN) / 4;
Expand All @@ -218,8 +224,8 @@ channel_seek_internal(struct sp_channel *channel, size_t pos, bool do_flush)
RETURN_ERROR(SP_ERR_DECRYPTION, sp_errmsg);

// Set the offset and received counter to match the seek
channel->file.offset_words = seek_words;
channel->file.received_words = seek_words;
channel->file.offset_bytes = 4 * seek_words;
channel->file.received_bytes = 4 * seek_words;

return 0;

Expand All @@ -241,22 +247,22 @@ channel_pause(struct sp_channel *channel)
channel->state = SP_CHANNEL_STATE_PAUSED;
}

// After a disconnect we connect to another one and try to resume. To make that
// work some data elements need to be reset.
// After a disconnect we connect to another AP and try to resume. To make that
// work during playback some data elements need to be reset.
void
channel_retry(struct sp_channel *channel)
{
size_t pos;

if (!channel)
if (!channel || channel->state != SP_CHANNEL_STATE_PLAYING)
return;

channel->is_data_mode = false;

memset(&channel->header, 0, sizeof(struct sp_channel_header));
memset(&channel->body, 0, sizeof(struct sp_channel_body));

pos = 4 * channel->file.received_words - SP_OGG_HEADER_LEN;
pos = channel->file.received_bytes - SP_OGG_HEADER_LEN;

channel_seek_internal(channel, pos, false); // false => don't flush
}
Expand Down Expand Up @@ -316,12 +322,12 @@ channel_header_handle(struct sp_channel *channel, struct sp_channel_header *head
}

memcpy(&be32, header->data, sizeof(be32));
channel->file.len_words = be32toh(be32);
channel->file.len_bytes = 4 * be32toh(be32);
}
}

static ssize_t
channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len, struct sp_session *session)
channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len)
{
ssize_t parsed_len;
ssize_t consumed_len;
Expand All @@ -333,10 +339,10 @@ channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg
if (msg_len == 0)
{
channel->file.end_of_chunk = true;
channel->file.end_of_file = (channel->file.received_words >= channel->file.len_words);
channel->file.end_of_file = (channel->file.received_bytes >= channel->file.len_bytes);

// In preparation for next chunk
channel->file.offset_words += SP_CHUNK_LEN_WORDS;
channel->file.offset_bytes += SP_CHUNK_LEN;
channel->is_data_mode = false;

return 0;
Expand Down Expand Up @@ -369,22 +375,19 @@ channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg
return ret;
}

static ssize_t
channel_data_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len, struct sp_session *session)
static int
channel_data_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len)
{
const char *errmsg;
int ret;

assert (msg_len % 4 == 0);

channel->file.received_words += msg_len / 4;
channel->file.received_bytes += msg_len;

ret = crypto_aes_decrypt(msg, msg_len, &channel->file.decrypt, &errmsg);
if (ret < 0)
RETURN_ERROR(SP_ERR_DECRYPTION, errmsg);

// Skip Spotify header
// TODO What to do here when seeking
if (!channel->is_spotify_header_received)
{
if (msg_len < SP_OGG_HEADER_LEN)
Expand Down Expand Up @@ -464,7 +467,7 @@ channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_s
msg_len -= sizeof(be);

// Will set data_mode, end_of_file and end_of_chunk as appropriate
consumed_len = channel_header_trailer_read(channel, msg, msg_len, session);
consumed_len = channel_header_trailer_read(channel, msg, msg_len);
if (consumed_len < 0)
RETURN_ERROR((int)consumed_len, sp_errmsg);

Expand All @@ -477,13 +480,31 @@ channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_s
if (!channel->is_data_mode || !(msg_len > 0))
return 0; // Not in data mode or no data to read

consumed_len = channel_data_read(channel, msg, msg_len, session);
if (consumed_len < 0)
RETURN_ERROR((int)consumed_len, sp_errmsg);
ret = channel_data_read(channel, msg, msg_len);
if (ret < 0)
RETURN_ERROR(ret, sp_errmsg);

return 0;

error:
return ret;
}

// With http there is the Spotify Ogg header, but no chunk header/trailer
int
channel_http_body_read(struct sp_channel *channel, uint8_t *body, size_t body_len)
{
int ret;

ret = channel_data_read(channel, body, body_len);
if (ret < 0)
goto error;

channel->file.end_of_chunk = true;
channel->file.end_of_file = (channel->file.received_bytes >= channel->file.len_bytes);
channel->file.offset_bytes += SP_CHUNK_LEN;
return 0;

error:
return ret;
}
3 changes: 3 additions & 0 deletions src/inputs/librespot-c/src/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ channel_retry(struct sp_channel *channel);

int
channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_session *session);

int
channel_http_body_read(struct sp_channel *channel, uint8_t *body, size_t body_len);
Loading

0 comments on commit 679e407

Please sign in to comment.