mirror of
https://github.com/owntone/owntone-server.git
synced 2025-11-12 14:51:38 -05:00
[spotify] Reintroduce libspotify support, change spotifyc to librespot-c
Select use of either libspotify or librespot-c as streaming backend via config option. librespot-c (renamed/improved spotifyc) impl has the following: - sync interface - seek support - honor bitrate config, set client and thread name - use web access token with "streaming" scope for login - fix issue with podcast playback Also say goodbye to file-based Spotify login.
This commit is contained in:
38
src/inputs/librespot-c/.gitignore
vendored
Normal file
38
src/inputs/librespot-c/.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
*~
|
||||
*.swp
|
||||
Makefile.in
|
||||
Makefile
|
||||
*.o
|
||||
*.lo
|
||||
*.a
|
||||
*.la
|
||||
.dirstamp
|
||||
.deps/
|
||||
.libs/
|
||||
|
||||
# autofoo stuff
|
||||
autom4te.cache
|
||||
aclocal.m4
|
||||
compile
|
||||
config.guess
|
||||
config.h
|
||||
config.h.in
|
||||
config.log
|
||||
config.status
|
||||
config.sub
|
||||
configure
|
||||
depcomp
|
||||
install-sh
|
||||
libtool
|
||||
ltmain.sh
|
||||
missing
|
||||
stamp-h1
|
||||
autotools-stamp
|
||||
build-stamp
|
||||
ar-lib
|
||||
|
||||
/.settings
|
||||
/.cproject
|
||||
/.project
|
||||
/.autotools
|
||||
/.vscode
|
||||
19
src/inputs/librespot-c/LICENSE
Normal file
19
src/inputs/librespot-c/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
26
src/inputs/librespot-c/Makefile.am
Normal file
26
src/inputs/librespot-c/Makefile.am
Normal file
@@ -0,0 +1,26 @@
|
||||
SUBDIRS = tests
|
||||
|
||||
noinst_LIBRARIES = librespot-c.a
|
||||
|
||||
SHANNON_SRC = \
|
||||
src/shannon/ShannonFast.c src/shannon/Shannon.h src/shannon/ShannonInternal.h
|
||||
|
||||
PROTO_SRC = \
|
||||
src/proto/keyexchange.pb-c.c src/proto/keyexchange.pb-c.h \
|
||||
src/proto/authentication.pb-c.c src/proto/authentication.pb-c.h \
|
||||
src/proto/mercury.pb-c.c src/proto/mercury.pb-c.h \
|
||||
src/proto/metadata.pb-c.c src/proto/metadata.pb-c.h
|
||||
|
||||
CORE_SRC = \
|
||||
src/librespot-c.c src/connection.c src/channel.c src/crypto.c src/commands.c
|
||||
|
||||
librespot_c_a_SOURCES = \
|
||||
$(CORE_SRC) \
|
||||
$(SHANNON_SRC) \
|
||||
$(PROTO_SRC)
|
||||
|
||||
noinst_HEADERS = \
|
||||
librespot-c.h src/librespot-c-internal.h src/connection.h \
|
||||
src/channel.h src/crypto.h src/commands.h
|
||||
|
||||
EXTRA_DIST = README.md LICENSE
|
||||
13
src/inputs/librespot-c/README.md
Normal file
13
src/inputs/librespot-c/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
Build:
|
||||
- autoreconf -i && ./configure && make
|
||||
|
||||
Test:
|
||||
- make check
|
||||
- ./tests/test1
|
||||
|
||||
Dependencies:
|
||||
- libevent-dev libgcrypt20-dev libcurl4-gnutls-dev libjson-c-dev libprotobuf-c-dev
|
||||
|
||||
Credits:
|
||||
- librespot (https://github.com/librespot-org/librespot)
|
||||
- timniederhausen for Shannon cipher (https://github.com/timniederhausen/shannon)
|
||||
22
src/inputs/librespot-c/configure.ac
Normal file
22
src/inputs/librespot-c/configure.ac
Normal file
@@ -0,0 +1,22 @@
|
||||
AC_INIT([librespot-c], [0.1])
|
||||
AC_CONFIG_AUX_DIR([.])
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects])
|
||||
AC_PROG_CC
|
||||
AM_PROG_AR
|
||||
AC_PROG_RANLIB
|
||||
|
||||
AC_CHECK_HEADERS_ONCE([endian.h sys/endian.h])
|
||||
AC_CHECK_DECL([htobe16], [],
|
||||
[AC_CHECK_HEADERS([libkern/OSByteOrder.h], [], [AC_MSG_ERROR([[Missing functions to swap byte order]])])],
|
||||
)
|
||||
|
||||
AC_SEARCH_LIBS([pthread_exit], [pthread], [], [AC_MSG_ERROR([[pthreads library is required]])])
|
||||
|
||||
PKG_CHECK_MODULES([LIBEVENT], [libevent])
|
||||
PKG_CHECK_MODULES([JSON_C], [json-c])
|
||||
PKG_CHECK_MODULES([LIBGCRYPT], [libgcrypt])
|
||||
PKG_CHECK_MODULES([LIBCURL], [libcurl])
|
||||
PKG_CHECK_MODULES([LIBPROTOBUF_C], [libprotobuf-c])
|
||||
|
||||
AC_CONFIG_FILES([Makefile tests/Makefile])
|
||||
AC_OUTPUT
|
||||
116
src/inputs/librespot-c/librespot-c.h
Normal file
116
src/inputs/librespot-c/librespot-c.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#ifndef __LIBRESPOT_C_H__
|
||||
#define __LIBRESPOT_C_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define LIBRESPOT_C_VERSION_MAJOR 0
|
||||
#define LIBRESPOT_C_VERSION_MINOR 1
|
||||
|
||||
|
||||
struct sp_session;
|
||||
|
||||
enum sp_bitrates
|
||||
{
|
||||
SP_BITRATE_ANY,
|
||||
SP_BITRATE_96,
|
||||
SP_BITRATE_160,
|
||||
SP_BITRATE_320,
|
||||
};
|
||||
|
||||
typedef void (*sp_progress_cb)(int fd, void *arg, size_t received, size_t len);
|
||||
|
||||
struct sp_credentials
|
||||
{
|
||||
char username[64];
|
||||
char password[32];
|
||||
|
||||
uint8_t stored_cred[256]; // Actual size is 146, but leave room for some more
|
||||
size_t stored_cred_len;
|
||||
uint8_t token[256]; // Actual size is ?
|
||||
size_t token_len;
|
||||
};
|
||||
|
||||
struct sp_metadata
|
||||
{
|
||||
size_t file_len;
|
||||
};
|
||||
|
||||
struct sp_sysinfo
|
||||
{
|
||||
char client_name[16];
|
||||
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);
|
||||
int (*tcp_connect)(const char *address, unsigned short port);
|
||||
void (*tcp_disconnect)(int fd);
|
||||
|
||||
// Optional - set name of thread
|
||||
void (*thread_name_set)(pthread_t thread);
|
||||
|
||||
// Debugging
|
||||
void (*hexdump)(const char *msg, uint8_t *data, size_t data_len);
|
||||
void (*logmsg)(const char *fmt, ...);
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct sp_session *
|
||||
librespotc_login_password(const char *username, const char *password);
|
||||
|
||||
struct sp_session *
|
||||
librespotc_login_stored_cred(const char *username, uint8_t *stored_cred, size_t stored_cred_len);
|
||||
|
||||
struct sp_session *
|
||||
librespotc_login_token(const char *username, const char *token);
|
||||
|
||||
int
|
||||
librespotc_logout(struct sp_session *session);
|
||||
|
||||
int
|
||||
librespotc_bitrate_set(struct sp_session *session, enum sp_bitrates bitrate);
|
||||
|
||||
int
|
||||
librespotc_credentials_get(struct sp_credentials *credentials, struct sp_session *session);
|
||||
|
||||
// Returns a file descriptor (in non-blocking mode) from which caller can read
|
||||
// one chunk of data. To get more data written/start playback loop, call
|
||||
// librespotc_play().
|
||||
int
|
||||
librespotc_open(const char *path, struct sp_session *session);
|
||||
|
||||
// Continues writing data to the file descriptor until error or end of track.
|
||||
// A read of the fd that returns 0 means end of track, and a negative read
|
||||
// return value means error. progress_cb and cb_arg optional.
|
||||
void
|
||||
librespotc_write(int fd, sp_progress_cb progress_cb, void *cb_arg);
|
||||
|
||||
// Seeks to pos (measured in bytes, so must not exceed file_len), flushes old
|
||||
// data from the fd and prepares one chunk of data for reading.
|
||||
int
|
||||
librespotc_seek(int fd, size_t pos);
|
||||
|
||||
// Closes a track download, incl. the fd.
|
||||
int
|
||||
librespotc_close(int fd);
|
||||
|
||||
int
|
||||
librespotc_metadata_get(struct sp_metadata *metadata, int fd);
|
||||
|
||||
const char *
|
||||
librespotc_last_errmsg(void);
|
||||
|
||||
int
|
||||
librespotc_init(struct sp_sysinfo *sysinfo, struct sp_callbacks *callbacks);
|
||||
|
||||
void
|
||||
librespotc_deinit(void);
|
||||
|
||||
#endif /* !__LIBRESPOT_C_H__ */
|
||||
446
src/inputs/librespot-c/src/channel.c
Normal file
446
src/inputs/librespot-c/src/channel.c
Normal file
@@ -0,0 +1,446 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "librespot-c-internal.h"
|
||||
|
||||
/* -------------------------------- Channels -------------------------------- */
|
||||
|
||||
/*
|
||||
Here is my current understanding of the channel concept:
|
||||
|
||||
1. A channel is established for retrieving chunks of audio. A channel is not a
|
||||
separate connection, all the traffic goes via the same Shannon-encrypted tcp
|
||||
connection as the rest.
|
||||
2. It depends on the cmd whether a channel is used. CmdStreamChunk,
|
||||
CmdStreamChunkRes, CmdChannelError, CmdChannelAbort use channels. A channel
|
||||
is identified with a uint16_t, which is the first 2 bytes of these packets.
|
||||
3. A channel is established with CmdStreamChunk where receiver picks channel id.
|
||||
Spotify responds with CmdStreamChunkRes that initially has some headers after
|
||||
the channel id. The headers are "reverse tlv": uint16_t header length,
|
||||
uint8_t header id, uint8_t header_data[]. The length includes the id length.
|
||||
4. After the headers are sent the channel switches to data mode. This is
|
||||
signalled by a header length of 0. In data mode Spotify sends the requested
|
||||
chunks of audio (CmdStreamChunkRes) which have the audio right after the
|
||||
channel id prefix. The audio is AES encrypted with a per-file key. An empty
|
||||
CmdStreamChunkRes indicates the end. The caller can then make a new
|
||||
CmdStreamChunk requesting the next data.
|
||||
5. For Ogg, the first 167 bytes of audio is a special Spotify header.
|
||||
6. The channel can presumably be reset with CmdChannelAbort (?)
|
||||
*/
|
||||
|
||||
static int
|
||||
path_to_media_id_and_type(struct sp_file *file)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
file->media_type = SP_MEDIA_UNKNOWN;
|
||||
if (strstr(file->path, ":track:"))
|
||||
file->media_type = SP_MEDIA_TRACK;
|
||||
else if (strstr(file->path, ":episode:"))
|
||||
file->media_type = SP_MEDIA_EPISODE;
|
||||
else
|
||||
return -1;
|
||||
|
||||
ptr = strrchr(file->path, ':');
|
||||
if (!ptr || strlen(ptr + 1) != 22)
|
||||
return -1;
|
||||
|
||||
return crypto_base62_to_bin(file->media_id, sizeof(file->media_id), ptr + 1);
|
||||
}
|
||||
|
||||
struct sp_channel *
|
||||
channel_get(uint32_t channel_id, struct sp_session *session)
|
||||
{
|
||||
if (channel_id > sizeof(session->channels)/sizeof(session->channels)[0])
|
||||
return NULL;
|
||||
|
||||
if (!session->channels[channel_id].is_allocated)
|
||||
return NULL;
|
||||
|
||||
return &session->channels[channel_id];
|
||||
}
|
||||
|
||||
void
|
||||
channel_free(struct sp_channel *channel)
|
||||
{
|
||||
if (!channel || !channel->is_allocated)
|
||||
return;
|
||||
|
||||
if (channel->audio_buf)
|
||||
evbuffer_free(channel->audio_buf);
|
||||
|
||||
if (channel->audio_write_ev)
|
||||
event_free(channel->audio_write_ev);
|
||||
|
||||
if (channel->audio_fd[0] >= 0)
|
||||
close(channel->audio_fd[0]);
|
||||
|
||||
if (channel->audio_fd[1] >= 0)
|
||||
close(channel->audio_fd[1]);
|
||||
|
||||
crypto_aes_free(&channel->file.decrypt);
|
||||
|
||||
free(channel->file.path);
|
||||
|
||||
memset(channel, 0, sizeof(struct sp_channel));
|
||||
|
||||
channel->audio_fd[0] = -1;
|
||||
channel->audio_fd[1] = -1;
|
||||
}
|
||||
|
||||
void
|
||||
channel_free_all(struct sp_session *session)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sizeof(session->channels)/sizeof(session->channels)[0]; i++)
|
||||
channel_free(&session->channels[i]);
|
||||
}
|
||||
|
||||
int
|
||||
channel_new(struct sp_channel **new_channel, struct sp_session *session, const char *path, struct event_base *evbase, event_callback_fn write_cb)
|
||||
{
|
||||
struct sp_channel *channel;
|
||||
uint16_t i = SP_DEFAULT_CHANNEL;
|
||||
int ret;
|
||||
|
||||
channel = &session->channels[i];
|
||||
|
||||
channel_free(channel);
|
||||
channel->id = i;
|
||||
channel->is_allocated = true;
|
||||
|
||||
channel->file.path = strdup(path);
|
||||
path_to_media_id_and_type(&channel->file);
|
||||
|
||||
// Set up the audio I/O
|
||||
ret = pipe(channel->audio_fd);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
if (fcntl(channel->audio_fd[0], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0)
|
||||
goto error;
|
||||
|
||||
if (fcntl(channel->audio_fd[1], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0)
|
||||
goto error;
|
||||
|
||||
channel->audio_write_ev = event_new(evbase, channel->audio_fd[1], EV_WRITE, write_cb, session);
|
||||
if (!channel->audio_write_ev)
|
||||
goto error;
|
||||
|
||||
channel->audio_buf = evbuffer_new();
|
||||
if (!channel->audio_buf)
|
||||
goto error;
|
||||
|
||||
*new_channel = channel;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
channel_free(channel);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set the fd to non-blocking in case the caller changed that, and then read
|
||||
// until empty
|
||||
static int
|
||||
channel_flush(int fd)
|
||||
{
|
||||
uint8_t buf[4096];
|
||||
int flags;
|
||||
int got;
|
||||
|
||||
flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags == -1)
|
||||
return -1;
|
||||
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
do
|
||||
got = read(fd, buf, sizeof(buf));
|
||||
while (got > 0);
|
||||
|
||||
fcntl(fd, F_SETFL, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
channel_play(struct sp_channel *channel)
|
||||
{
|
||||
channel->is_writing = true;
|
||||
}
|
||||
|
||||
void
|
||||
channel_stop(struct sp_channel *channel)
|
||||
{
|
||||
channel->is_writing = false;
|
||||
|
||||
// This will tell the reader that there is no more to read. He should then
|
||||
// call librespotc_close(), which will clean up the rest of the channel via
|
||||
// channel_free().
|
||||
close(channel->audio_fd[1]);
|
||||
channel->audio_fd[1] = -1;
|
||||
}
|
||||
|
||||
int
|
||||
channel_seek(struct sp_channel *channel, size_t pos)
|
||||
{
|
||||
uint32_t seek_words;
|
||||
int ret;
|
||||
|
||||
ret = channel_flush(channel->audio_fd[0]);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Could not flush read fd before seeking");
|
||||
|
||||
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.
|
||||
channel->seek_align = (pos + SP_OGG_HEADER_LEN) % 4;
|
||||
|
||||
seek_words = (pos + SP_OGG_HEADER_LEN) / 4;
|
||||
|
||||
ret = crypto_aes_seek(&channel->file.decrypt, 4 * seek_words, &sp_errmsg);
|
||||
if (ret < 0)
|
||||
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;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
channel_pause(struct sp_channel *channel)
|
||||
{
|
||||
channel_flush(channel->audio_fd[0]);
|
||||
|
||||
channel->is_writing = false;
|
||||
}
|
||||
|
||||
// Always returns number of byte read so caller can advance read pointer. If
|
||||
// header->len == 0 is returned it means that there are no more headers, and
|
||||
// caller should switch the channel to data mode.
|
||||
static ssize_t
|
||||
channel_header_parse(struct sp_channel_header *header, uint8_t *data, size_t data_len)
|
||||
{
|
||||
uint8_t *ptr;
|
||||
uint16_t be;
|
||||
|
||||
if (data_len < sizeof(be))
|
||||
return -1;
|
||||
|
||||
ptr = data;
|
||||
memset(header, 0, sizeof(struct sp_channel_header));
|
||||
|
||||
memcpy(&be, ptr, sizeof(be));
|
||||
header->len = be16toh(be);
|
||||
ptr += sizeof(be);
|
||||
|
||||
if (header->len == 0)
|
||||
goto done; // No more headers
|
||||
else if (data_len < header->len + sizeof(be))
|
||||
return -1;
|
||||
|
||||
header->id = ptr[0];
|
||||
ptr += 1;
|
||||
|
||||
header->data = ptr;
|
||||
header->data_len = header->len - 1;
|
||||
ptr += header->data_len;
|
||||
|
||||
assert(ptr - data == header->len + sizeof(be));
|
||||
|
||||
done:
|
||||
return header->len + sizeof(be);
|
||||
}
|
||||
|
||||
static void
|
||||
channel_header_handle(struct sp_channel *channel, struct sp_channel_header *header)
|
||||
{
|
||||
uint32_t be32;
|
||||
|
||||
sp_cb.hexdump("Received header\n", header->data, header->data_len);
|
||||
|
||||
// The only header that librespot seems to use is 0x3, which is the audio file
|
||||
// size in words (incl. headers?)
|
||||
if (header->id == 0x3)
|
||||
{
|
||||
if (header->data_len != sizeof(be32))
|
||||
{
|
||||
sp_cb.logmsg("Unexpected header length for header id 0x3\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&be32, header->data, sizeof(be32));
|
||||
channel->file.len_words = be32toh(be32);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len, struct sp_session *session)
|
||||
{
|
||||
ssize_t parsed_len;
|
||||
ssize_t consumed_len;
|
||||
int ret;
|
||||
|
||||
channel->file.end_of_chunk = false;
|
||||
channel->file.end_of_file = false;
|
||||
|
||||
if (msg_len == 0)
|
||||
{
|
||||
channel->file.end_of_chunk = true;
|
||||
channel->file.end_of_file = (channel->file.received_words >= channel->file.len_words);
|
||||
|
||||
// In preparation for next chunk
|
||||
channel->file.offset_words += SP_CHUNK_LEN_WORDS;
|
||||
channel->is_data_mode = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if (channel->is_data_mode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (consumed_len = 0; msg_len > 0; msg += parsed_len, msg_len -= parsed_len)
|
||||
{
|
||||
parsed_len = channel_header_parse(&channel->header, msg, msg_len);
|
||||
if (parsed_len < 0)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Invalid channel header");
|
||||
|
||||
consumed_len += parsed_len;
|
||||
|
||||
if (channel->header.len == 0)
|
||||
{
|
||||
channel->is_data_mode = true;
|
||||
break; // All headers read
|
||||
}
|
||||
|
||||
channel_header_handle(channel, &channel->header);
|
||||
}
|
||||
|
||||
return consumed_len;
|
||||
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
channel_data_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len, struct sp_session *session)
|
||||
{
|
||||
const char *errmsg;
|
||||
int ret;
|
||||
|
||||
assert (msg_len % 4 == 0);
|
||||
|
||||
channel->file.received_words += msg_len / 4;
|
||||
|
||||
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)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Invalid data received");
|
||||
|
||||
channel->is_spotify_header_received = true;
|
||||
|
||||
msg += SP_OGG_HEADER_LEN;
|
||||
msg_len -= SP_OGG_HEADER_LEN;
|
||||
}
|
||||
|
||||
// See explanation of this in channel_seek()
|
||||
if (channel->seek_align)
|
||||
{
|
||||
msg += channel->seek_align;
|
||||
msg_len -= channel->seek_align;
|
||||
channel->seek_align = 0;
|
||||
}
|
||||
|
||||
channel->body.data = msg;
|
||||
channel->body.data_len = msg_len;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
channel_data_write(struct sp_channel *channel)
|
||||
{
|
||||
ssize_t wrote;
|
||||
int ret;
|
||||
|
||||
wrote = evbuffer_write(channel->audio_buf, channel->audio_fd[1]);
|
||||
if (wrote < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
|
||||
return SP_OK_WAIT;
|
||||
else if (wrote < 0)
|
||||
RETURN_ERROR(SP_ERR_WRITE, "Error writing to audio pipe");
|
||||
|
||||
channel->audio_written_len += wrote;
|
||||
|
||||
if (evbuffer_get_length(channel->audio_buf) > 0)
|
||||
return SP_OK_WAIT;
|
||||
|
||||
return SP_OK_DONE;
|
||||
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_session *session)
|
||||
{
|
||||
struct sp_channel *channel;
|
||||
uint16_t be;
|
||||
ssize_t consumed_len;
|
||||
int ret;
|
||||
|
||||
if (msg_len < sizeof(be))
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Chunk response is too small");
|
||||
|
||||
memcpy(&be, msg, sizeof(be));
|
||||
*channel_id = be16toh(be);
|
||||
|
||||
channel = channel_get(*channel_id, session);
|
||||
if (!channel)
|
||||
{
|
||||
sp_cb.hexdump("Message with unknown channel\n", msg, msg_len);
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Could not recognize channel in chunk response");
|
||||
}
|
||||
|
||||
msg += sizeof(be);
|
||||
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);
|
||||
if (consumed_len < 0)
|
||||
RETURN_ERROR((int)consumed_len, sp_errmsg);
|
||||
|
||||
msg += consumed_len;
|
||||
msg_len -= consumed_len;
|
||||
|
||||
channel->body.data = NULL;
|
||||
channel->body.data_len = 0;
|
||||
|
||||
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);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
29
src/inputs/librespot-c/src/channel.h
Normal file
29
src/inputs/librespot-c/src/channel.h
Normal file
@@ -0,0 +1,29 @@
|
||||
struct sp_channel *
|
||||
channel_get(uint32_t channel_id, struct sp_session *session);
|
||||
|
||||
void
|
||||
channel_free(struct sp_channel *channel);
|
||||
|
||||
void
|
||||
channel_free_all(struct sp_session *session);
|
||||
|
||||
int
|
||||
channel_new(struct sp_channel **channel, struct sp_session *session, const char *path, struct event_base *evbase, event_callback_fn write_cb);
|
||||
|
||||
int
|
||||
channel_data_write(struct sp_channel *channel);
|
||||
|
||||
void
|
||||
channel_play(struct sp_channel *channel);
|
||||
|
||||
void
|
||||
channel_stop(struct sp_channel *channel);
|
||||
|
||||
int
|
||||
channel_seek(struct sp_channel *channel, size_t pos);
|
||||
|
||||
void
|
||||
channel_pause(struct sp_channel *channel);
|
||||
|
||||
int
|
||||
channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_session *session);
|
||||
423
src/inputs/librespot-c/src/commands.c
Normal file
423
src/inputs/librespot-c/src/commands.c
Normal file
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Christian Meffert <christian.meffert@googlemail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "commands.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct command
|
||||
{
|
||||
pthread_mutex_t lck;
|
||||
pthread_cond_t cond;
|
||||
|
||||
command_function func;
|
||||
command_function func_bh;
|
||||
void *arg;
|
||||
int nonblock;
|
||||
int ret;
|
||||
int pending;
|
||||
};
|
||||
|
||||
struct commands_base
|
||||
{
|
||||
struct event_base *evbase;
|
||||
command_exit_cb exit_cb;
|
||||
int command_pipe[2];
|
||||
struct event *command_event;
|
||||
struct command *current_cmd;
|
||||
};
|
||||
|
||||
static int
|
||||
mutex_init(pthread_mutex_t *mutex)
|
||||
{
|
||||
pthread_mutexattr_t mattr;
|
||||
int err;
|
||||
|
||||
pthread_mutexattr_init(&mattr);
|
||||
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK);
|
||||
err = pthread_mutex_init(mutex, &mattr);
|
||||
pthread_mutexattr_destroy(&mattr);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Asynchronous execution of the command function
|
||||
*/
|
||||
static void
|
||||
command_cb_async(struct commands_base *cmdbase, struct command *cmd)
|
||||
{
|
||||
enum command_state cmdstate;
|
||||
|
||||
// Command is executed asynchronously
|
||||
cmdstate = cmd->func(cmd->arg, &cmd->ret);
|
||||
|
||||
// Only free arg if there are no pending events (used in worker.c)
|
||||
if (cmdstate != COMMAND_PENDING && cmd->arg)
|
||||
free(cmd->arg);
|
||||
|
||||
free(cmd);
|
||||
|
||||
event_add(cmdbase->command_event, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Synchronous execution of the command function
|
||||
*/
|
||||
static void
|
||||
command_cb_sync(struct commands_base *cmdbase, struct command *cmd)
|
||||
{
|
||||
enum command_state cmdstate;
|
||||
|
||||
pthread_mutex_lock(&cmd->lck);
|
||||
|
||||
cmdstate = cmd->func(cmd->arg, &cmd->ret);
|
||||
if (cmdstate == COMMAND_PENDING)
|
||||
{
|
||||
// Command execution is waiting for pending events before returning to the caller
|
||||
cmdbase->current_cmd = cmd;
|
||||
cmd->pending = cmd->ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command execution finished, execute the bottom half function
|
||||
if (cmd->ret == 0 && cmd->func_bh)
|
||||
cmd->func_bh(cmd->arg, &cmd->ret);
|
||||
|
||||
event_add(cmdbase->command_event, NULL);
|
||||
|
||||
// Signal the calling thread that the command execution finished
|
||||
pthread_cond_signal(&cmd->cond);
|
||||
pthread_mutex_unlock(&cmd->lck);
|
||||
|
||||
// Note if cmd->func was cmdloop_exit then cmdbase may be invalid now,
|
||||
// because commands_base_destroy() may have freed it
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Event callback function
|
||||
*
|
||||
* Function is triggered by libevent if there is data to read on the command pipe (writing to the command pipe happens through
|
||||
* the send_command function).
|
||||
*/
|
||||
static void
|
||||
command_cb(int fd, short what, void *arg)
|
||||
{
|
||||
struct commands_base *cmdbase;
|
||||
struct command *cmd;
|
||||
int ret;
|
||||
|
||||
cmdbase = arg;
|
||||
|
||||
// Get the command to execute from the pipe
|
||||
ret = read(cmdbase->command_pipe[0], &cmd, sizeof(cmd));
|
||||
if (ret != sizeof(cmd))
|
||||
{
|
||||
// Incorrect length, ignore
|
||||
event_add(cmdbase->command_event, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the command function
|
||||
if (cmd->nonblock)
|
||||
{
|
||||
// Command is executed asynchronously
|
||||
command_cb_async(cmdbase, cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command is executed synchronously, caller is waiting until signaled that the execution finished
|
||||
command_cb_sync(cmdbase, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes the given command to the command pipe
|
||||
*/
|
||||
static int
|
||||
send_command(struct commands_base *cmdbase, struct command *cmd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!cmd->func)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = write(cmdbase->command_pipe[1], &cmd, sizeof(cmd));
|
||||
if (ret != sizeof(cmd))
|
||||
{
|
||||
// errno set by write()
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees the command base and closes the (internally used) pipes
|
||||
*/
|
||||
int
|
||||
commands_base_free(struct commands_base *cmdbase)
|
||||
{
|
||||
if (cmdbase->command_event)
|
||||
event_free(cmdbase->command_event);
|
||||
|
||||
close(cmdbase->command_pipe[0]);
|
||||
close(cmdbase->command_pipe[1]);
|
||||
free(cmdbase);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a new command base, needs to be freed by commands_base_destroy or commands_base_free.
|
||||
*
|
||||
* @param evbase The libevent base to use for command handling
|
||||
* @param exit_cb Optional callback function to be called during commands_base_destroy
|
||||
*/
|
||||
struct commands_base *
|
||||
commands_base_new(struct event_base *evbase, command_exit_cb exit_cb)
|
||||
{
|
||||
struct commands_base *cmdbase;
|
||||
int ret;
|
||||
|
||||
cmdbase = calloc(1, sizeof(struct commands_base));
|
||||
|
||||
#ifdef HAVE_PIPE2
|
||||
ret = pipe2(cmdbase->command_pipe, O_CLOEXEC);
|
||||
#else
|
||||
ret = pipe(cmdbase->command_pipe);
|
||||
#endif
|
||||
if (ret < 0)
|
||||
{
|
||||
// errno set by pipe
|
||||
free(cmdbase);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cmdbase->command_event = event_new(evbase, cmdbase->command_pipe[0], EV_READ, command_cb, cmdbase);
|
||||
if (!cmdbase->command_event)
|
||||
{
|
||||
commands_base_free(cmdbase);
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = event_add(cmdbase->command_event, NULL);
|
||||
if (ret != 0)
|
||||
{
|
||||
commands_base_free(cmdbase);
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cmdbase->evbase = evbase;
|
||||
cmdbase->exit_cb = exit_cb;
|
||||
|
||||
return cmdbase;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the current return value for the current pending command.
|
||||
*
|
||||
* If a command has more than one pending event, each event can access the previous set return value
|
||||
* if it depends on it.
|
||||
*
|
||||
* @param cmdbase The command base
|
||||
* @return The current return value
|
||||
*/
|
||||
int
|
||||
commands_exec_returnvalue(struct commands_base *cmdbase)
|
||||
{
|
||||
if (cmdbase->current_cmd == NULL)
|
||||
return 0;
|
||||
|
||||
return cmdbase->current_cmd->ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a command function returned COMMAND_PENDING, each event triggered by this command needs to
|
||||
* call command_exec_end, passing it the return value of the event execution.
|
||||
*
|
||||
* If a command function is waiting for multiple events, each event needs to call command_exec_end.
|
||||
* The command base keeps track of the number of still pending events and only returns to the caller
|
||||
* if there are no pending events left.
|
||||
*
|
||||
* @param cmdbase The command base (holds the current pending command)
|
||||
* @param retvalue The return value for the calling thread
|
||||
*/
|
||||
void
|
||||
commands_exec_end(struct commands_base *cmdbase, int retvalue)
|
||||
{
|
||||
struct command *current_cmd = cmdbase->current_cmd;
|
||||
|
||||
if (!current_cmd)
|
||||
return;
|
||||
|
||||
// A pending event finished, decrease the number of pending events and update the return value
|
||||
current_cmd->pending--;
|
||||
current_cmd->ret = retvalue;
|
||||
|
||||
// If there are still pending events return
|
||||
if (current_cmd->pending > 0)
|
||||
return;
|
||||
|
||||
// All pending events have finished, execute the bottom half and signal the caller that the command execution finished
|
||||
if (current_cmd->func_bh)
|
||||
current_cmd->func_bh(current_cmd->arg, ¤t_cmd->ret);
|
||||
|
||||
cmdbase->current_cmd = NULL;
|
||||
|
||||
/* Process commands again */
|
||||
event_add(cmdbase->command_event, NULL);
|
||||
|
||||
pthread_cond_signal(¤t_cmd->cond);
|
||||
pthread_mutex_unlock(¤t_cmd->lck);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute the function 'func' with the given argument 'arg' in the event loop thread.
|
||||
* Blocks the caller (thread) until the function returned.
|
||||
*
|
||||
* If a function 'func_bh' ("bottom half") is given, it is executed after 'func' has successfully
|
||||
* finished.
|
||||
*
|
||||
* @param cmdbase The command base
|
||||
* @param func The function to be executed
|
||||
* @param func_bh The bottom half function to be executed after all pending events from func are processed
|
||||
* @param arg Argument passed to func (and func_bh)
|
||||
* @return Return value of func (or func_bh if func_bh is not NULL)
|
||||
*/
|
||||
int
|
||||
commands_exec_sync(struct commands_base *cmdbase, command_function func, command_function func_bh, void *arg)
|
||||
{
|
||||
struct command cmd;
|
||||
int errsv = 0;
|
||||
int ret;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct command));
|
||||
cmd.func = func;
|
||||
cmd.func_bh = func_bh;
|
||||
cmd.arg = arg;
|
||||
cmd.nonblock = 0;
|
||||
|
||||
mutex_init(&cmd.lck);
|
||||
pthread_cond_init(&cmd.cond, NULL);
|
||||
|
||||
pthread_mutex_lock(&cmd.lck);
|
||||
|
||||
ret = send_command(cmdbase, &cmd);
|
||||
if (ret < 0)
|
||||
{
|
||||
errsv = errno;
|
||||
cmd.ret = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_cond_wait(&cmd.cond, &cmd.lck);
|
||||
}
|
||||
|
||||
// May change errno, but we don't care about that
|
||||
pthread_mutex_unlock(&cmd.lck);
|
||||
|
||||
pthread_cond_destroy(&cmd.cond);
|
||||
pthread_mutex_destroy(&cmd.lck);
|
||||
|
||||
errno = errsv;
|
||||
return cmd.ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute the function 'func' with the given argument 'arg' in the event loop thread.
|
||||
* Triggers the function execution and immediately returns (does not wait for func to finish).
|
||||
*
|
||||
* The pointer passed as argument is freed in the event loop thread after func returned.
|
||||
*
|
||||
* @param cmdbase The command base
|
||||
* @param func The function to be executed
|
||||
* @param arg Argument passed to func
|
||||
* @return 0 if triggering the function execution succeeded, -1 on failure.
|
||||
*/
|
||||
int
|
||||
commands_exec_async(struct commands_base *cmdbase, command_function func, void *arg)
|
||||
{
|
||||
struct command *cmd;
|
||||
int ret;
|
||||
|
||||
cmd = calloc(1, sizeof(struct command));
|
||||
cmd->func = func;
|
||||
cmd->func_bh = NULL;
|
||||
cmd->arg = arg;
|
||||
cmd->nonblock = 1;
|
||||
|
||||
ret = send_command(cmdbase, cmd);
|
||||
if (ret < 0)
|
||||
{
|
||||
free(cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Command to break the libevent loop
|
||||
*
|
||||
* If the command base was created with an exit_cb function, exit_cb is called before breaking the
|
||||
* libevent loop.
|
||||
*
|
||||
* @param arg The command base
|
||||
* @param retval Always set to COMMAND_END
|
||||
*/
|
||||
static enum command_state
|
||||
cmdloop_exit(void *arg, int *retval)
|
||||
{
|
||||
struct commands_base *cmdbase = arg;
|
||||
*retval = 0;
|
||||
|
||||
if (cmdbase->exit_cb)
|
||||
cmdbase->exit_cb();
|
||||
|
||||
event_base_loopbreak(cmdbase->evbase);
|
||||
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
/*
|
||||
* Break the libevent loop for the given command base, closes the internally used pipes
|
||||
* and frees the command base.
|
||||
*
|
||||
* @param cmdbase The command base
|
||||
*/
|
||||
void
|
||||
commands_base_destroy(struct commands_base *cmdbase)
|
||||
{
|
||||
commands_exec_sync(cmdbase, cmdloop_exit, NULL, cmdbase);
|
||||
commands_base_free(cmdbase);
|
||||
}
|
||||
|
||||
56
src/inputs/librespot-c/src/commands.h
Normal file
56
src/inputs/librespot-c/src/commands.h
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
#ifndef SRC_COMMANDS_H_
|
||||
#define SRC_COMMANDS_H_
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
enum command_state {
|
||||
COMMAND_END = 0,
|
||||
COMMAND_PENDING = 1,
|
||||
};
|
||||
|
||||
/*
|
||||
* Function that will be executed in the event loop thread.
|
||||
*
|
||||
* If the function has pending events to complete, it needs to return
|
||||
* COMMAND_PENDING with 'ret' set to the number of pending events to wait for.
|
||||
*
|
||||
* If the function returns with COMMAND_END, command execution will proceed
|
||||
* with the "bottem half" function (if passed to the command_exec function) only
|
||||
* if 'ret' is 0.
|
||||
*
|
||||
* @param arg Opaque pointer passed by command_exec_sync or command_exec_async
|
||||
* @param ret Pointer to the return value for the caller of the command
|
||||
* @return COMMAND_END if there are no pending events (function execution is
|
||||
* complete) or COMMAND_PENDING if there are pending events
|
||||
*/
|
||||
typedef enum command_state (*command_function)(void *arg, int *ret);
|
||||
|
||||
typedef void (*command_exit_cb)(void);
|
||||
|
||||
|
||||
struct commands_base;
|
||||
|
||||
|
||||
struct commands_base *
|
||||
commands_base_new(struct event_base *evbase, command_exit_cb exit_cb);
|
||||
|
||||
int
|
||||
commands_base_free(struct commands_base *cmdbase);
|
||||
|
||||
int
|
||||
commands_exec_returnvalue(struct commands_base *cmdbase);
|
||||
|
||||
void
|
||||
commands_exec_end(struct commands_base *cmdbase, int retvalue);
|
||||
|
||||
int
|
||||
commands_exec_sync(struct commands_base *cmdbase, command_function func, command_function func_bh, void *arg);
|
||||
|
||||
int
|
||||
commands_exec_async(struct commands_base *cmdbase, command_function func, void *arg);
|
||||
|
||||
void
|
||||
commands_base_destroy(struct commands_base *cmdbase);
|
||||
|
||||
#endif /* SRC_COMMANDS_H_ */
|
||||
1342
src/inputs/librespot-c/src/connection.c
Normal file
1342
src/inputs/librespot-c/src/connection.c
Normal file
File diff suppressed because it is too large
Load Diff
17
src/inputs/librespot-c/src/connection.h
Normal file
17
src/inputs/librespot-c/src/connection.h
Normal file
@@ -0,0 +1,17 @@
|
||||
void
|
||||
ap_disconnect(struct sp_connection *conn);
|
||||
|
||||
enum sp_error
|
||||
ap_connect(enum sp_msg_type type, struct sp_conn_callbacks *cb, struct sp_session *session);
|
||||
|
||||
enum sp_error
|
||||
response_read(struct sp_session *session);
|
||||
|
||||
int
|
||||
msg_make(struct sp_message *msg, enum sp_msg_type type, struct sp_session *session);
|
||||
|
||||
int
|
||||
msg_send(struct sp_message *msg, struct sp_connection *conn);
|
||||
|
||||
int
|
||||
msg_pong(struct sp_session *session);
|
||||
464
src/inputs/librespot-c/src/crypto.c
Normal file
464
src/inputs/librespot-c/src/crypto.c
Normal file
@@ -0,0 +1,464 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h> // for isdigit(), isupper(), islower()
|
||||
|
||||
#include "librespot-c-internal.h" // For endian compat functions
|
||||
#include "crypto.h"
|
||||
|
||||
|
||||
/* ----------------------------------- Crypto ------------------------------- */
|
||||
|
||||
#define SHA512_DIGEST_LENGTH 64
|
||||
#define bnum_new(bn) \
|
||||
do { \
|
||||
if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { \
|
||||
if (!gcry_check_version("1.5.4")) \
|
||||
abort(); \
|
||||
gcry_control(GCRYCTL_DISABLE_SECMEM, 0); \
|
||||
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); \
|
||||
} \
|
||||
bn = gcry_mpi_new(1); \
|
||||
} while (0)
|
||||
#define bnum_free(bn) gcry_mpi_release(bn)
|
||||
#define bnum_num_bytes(bn) (gcry_mpi_get_nbits(bn) + 7) / 8
|
||||
#define bnum_is_zero(bn) (gcry_mpi_cmp_ui(bn, (unsigned long)0) == 0)
|
||||
#define bnum_bn2bin(bn, buf, len) gcry_mpi_print(GCRYMPI_FMT_USG, buf, len, NULL, bn)
|
||||
#define bnum_bin2bn(bn, buf, len) gcry_mpi_scan(&bn, GCRYMPI_FMT_USG, buf, len, NULL)
|
||||
#define bnum_hex2bn(bn, buf) gcry_mpi_scan(&bn, GCRYMPI_FMT_HEX, buf, 0, 0)
|
||||
#define bnum_random(bn, num_bits) gcry_mpi_randomize(bn, num_bits, GCRY_WEAK_RANDOM)
|
||||
#define bnum_add(bn, a, b) gcry_mpi_add(bn, a, b)
|
||||
#define bnum_sub(bn, a, b) gcry_mpi_sub(bn, a, b)
|
||||
#define bnum_mul(bn, a, b) gcry_mpi_mul(bn, a, b)
|
||||
#define bnum_mod(bn, a, b) gcry_mpi_mod(bn, a, b)
|
||||
typedef gcry_mpi_t bnum;
|
||||
__attribute__((unused)) static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p)
|
||||
{
|
||||
gcry_mpi_powm(bn, y, q, p);
|
||||
}
|
||||
__attribute__((unused)) static void bnum_modadd(bnum bn, bnum a, bnum b, bnum m)
|
||||
{
|
||||
gcry_mpi_addm(bn, a, b, m);
|
||||
}
|
||||
|
||||
static const uint8_t generator_bytes[] = { 0x2 };
|
||||
static const uint8_t prime_bytes[] =
|
||||
{
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34,
|
||||
0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74,
|
||||
0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
|
||||
0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37,
|
||||
0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6,
|
||||
0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
};
|
||||
|
||||
static void
|
||||
crypto_log(const char *fmt, ...)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
static void
|
||||
crypto_hexdump(const char *msg, uint8_t *mem, size_t len)
|
||||
{
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
int
|
||||
crypto_keys_set(struct crypto_keys *keys)
|
||||
{
|
||||
bnum generator;
|
||||
bnum prime;
|
||||
bnum private_key;
|
||||
bnum public_key;
|
||||
|
||||
bnum_bin2bn(generator, generator_bytes, sizeof(generator_bytes));
|
||||
bnum_bin2bn(prime, prime_bytes, sizeof(prime_bytes));
|
||||
bnum_new(private_key);
|
||||
bnum_new(public_key);
|
||||
|
||||
// bnum_random(private_key, 8 * (sizeof(keys->private_key) - 1)); // Not sure why it is 95 bytes?
|
||||
bnum_random(private_key, 8 * sizeof(keys->private_key));
|
||||
|
||||
bnum_modexp(public_key, generator, private_key, prime);
|
||||
|
||||
memset(keys, 0, sizeof(struct crypto_keys));
|
||||
bnum_bn2bin(private_key, keys->private_key, sizeof(keys->private_key));
|
||||
bnum_bn2bin(public_key, keys->public_key, sizeof(keys->public_key));
|
||||
|
||||
bnum_free(generator);
|
||||
bnum_free(prime);
|
||||
bnum_free(private_key);
|
||||
bnum_free(public_key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
crypto_shared_secret(uint8_t **shared_secret_bytes, size_t *shared_secret_bytes_len,
|
||||
uint8_t *private_key_bytes, size_t private_key_bytes_len,
|
||||
uint8_t *server_key_bytes, size_t server_key_bytes_len)
|
||||
{
|
||||
bnum private_key;
|
||||
bnum server_key;
|
||||
bnum prime;
|
||||
bnum shared_secret;
|
||||
|
||||
bnum_bin2bn(private_key, private_key_bytes, private_key_bytes_len);
|
||||
bnum_bin2bn(server_key, server_key_bytes, server_key_bytes_len);
|
||||
bnum_bin2bn(prime, prime_bytes, sizeof(prime_bytes));
|
||||
bnum_new(shared_secret);
|
||||
|
||||
bnum_modexp(shared_secret, server_key, private_key, prime);
|
||||
|
||||
*shared_secret_bytes_len = bnum_num_bytes(shared_secret);
|
||||
*shared_secret_bytes = malloc(*shared_secret_bytes_len);
|
||||
bnum_bn2bin(shared_secret, *shared_secret_bytes, *shared_secret_bytes_len);
|
||||
|
||||
bnum_free(private_key);
|
||||
bnum_free(server_key);
|
||||
bnum_free(prime);
|
||||
bnum_free(shared_secret);
|
||||
}
|
||||
|
||||
// Calculates challenge and send/receive keys. The challenge is allocated,
|
||||
// caller must free
|
||||
int
|
||||
crypto_challenge(uint8_t **challenge, size_t *challenge_len,
|
||||
uint8_t *send_key, size_t send_key_len,
|
||||
uint8_t *recv_key, size_t recv_key_len,
|
||||
uint8_t *packets, size_t packets_len,
|
||||
uint8_t *shared_secret, size_t shared_secret_len)
|
||||
{
|
||||
gcry_mac_hd_t hd = NULL;
|
||||
uint8_t data[0x64];
|
||||
uint8_t i;
|
||||
size_t offset;
|
||||
size_t len;
|
||||
|
||||
if (gcry_mac_open(&hd, GCRY_MAC_HMAC_SHA1, 0, NULL) != GPG_ERR_NO_ERROR)
|
||||
goto error;
|
||||
|
||||
if (gcry_mac_setkey(hd, shared_secret, shared_secret_len) != GPG_ERR_NO_ERROR)
|
||||
goto error;
|
||||
|
||||
offset = 0;
|
||||
for (i = 1; i <= 6; i++)
|
||||
{
|
||||
gcry_mac_write(hd, packets, packets_len);
|
||||
gcry_mac_write(hd, &i, sizeof(i));
|
||||
len = sizeof(data) - offset;
|
||||
gcry_mac_read(hd, data + offset, &len);
|
||||
offset += len;
|
||||
gcry_mac_reset(hd);
|
||||
}
|
||||
|
||||
gcry_mac_close(hd);
|
||||
hd = NULL;
|
||||
|
||||
assert(send_key_len == 32);
|
||||
assert(recv_key_len == 32);
|
||||
|
||||
memcpy(send_key, data + 20, send_key_len);
|
||||
memcpy(recv_key, data + 52, recv_key_len);
|
||||
|
||||
// Calculate challenge
|
||||
if (gcry_mac_open(&hd, GCRY_MAC_HMAC_SHA1, 0, NULL) != GPG_ERR_NO_ERROR)
|
||||
goto error;
|
||||
|
||||
if (gcry_mac_setkey(hd, data, 20) != GPG_ERR_NO_ERROR)
|
||||
goto error;
|
||||
|
||||
gcry_mac_write(hd, packets, packets_len);
|
||||
|
||||
*challenge_len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA1);
|
||||
*challenge = malloc(*challenge_len);
|
||||
gcry_mac_read(hd, *challenge, challenge_len);
|
||||
gcry_mac_close(hd);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (hd)
|
||||
gcry_mac_close(hd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Inplace encryption, buf_len must be larger than plain_len so that the mac
|
||||
// can be added
|
||||
ssize_t
|
||||
crypto_encrypt(uint8_t *buf, size_t buf_len, size_t plain_len, struct crypto_cipher *cipher)
|
||||
{
|
||||
uint32_t nonce;
|
||||
uint8_t mac[4];
|
||||
size_t encrypted_len;
|
||||
|
||||
encrypted_len = plain_len + sizeof(mac);
|
||||
if (encrypted_len > buf_len)
|
||||
return -1;
|
||||
|
||||
shn_key(&cipher->shannon, cipher->key, sizeof(cipher->key));
|
||||
|
||||
nonce = htobe32(cipher->nonce);
|
||||
shn_nonce(&cipher->shannon, (uint8_t *)&nonce, sizeof(nonce));
|
||||
|
||||
shn_encrypt(&cipher->shannon, buf, plain_len);
|
||||
shn_finish(&cipher->shannon, mac, sizeof(mac));
|
||||
|
||||
memcpy(buf + plain_len, mac, sizeof(mac));
|
||||
|
||||
cipher->nonce++;
|
||||
|
||||
return encrypted_len;
|
||||
}
|
||||
|
||||
static size_t
|
||||
payload_len_get(uint8_t *header)
|
||||
{
|
||||
uint16_t be;
|
||||
memcpy(&be, header + 1, sizeof(be));
|
||||
return (size_t)be16toh(be);
|
||||
}
|
||||
|
||||
// *encrypted will consist of a header (3 bytes, encrypted), payload length (2
|
||||
// bytes, encrypted, BE), the encrypted payload and then the mac (4 bytes, not
|
||||
// encrypted). The return will be the number of bytes decrypted (incl mac if a
|
||||
// whole packet was decrypted). Zero means not enough data for a packet.
|
||||
ssize_t
|
||||
crypto_decrypt(uint8_t *encrypted, size_t encrypted_len, struct crypto_cipher *cipher)
|
||||
{
|
||||
uint32_t nonce;
|
||||
uint8_t mac[4];
|
||||
size_t header_len = sizeof(cipher->last_header);
|
||||
size_t payload_len;
|
||||
|
||||
crypto_log("Decrypting %zu bytes with nonce %u\n", encrypted_len, cipher->nonce);
|
||||
// crypto_hexdump("Key\n", cipher->key, sizeof(cipher->key));
|
||||
// crypto_hexdump("Encrypted\n", encrypted, encrypted_len);
|
||||
|
||||
// In case we didn't even receive the basics, header and mac, then return.
|
||||
if (encrypted_len < header_len + sizeof(mac))
|
||||
{
|
||||
crypto_log("Waiting for %zu header bytes, have %zu\n", header_len + sizeof(mac), encrypted_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Will be zero if this is the first pass
|
||||
payload_len = payload_len_get(cipher->last_header);
|
||||
if (!payload_len)
|
||||
{
|
||||
shn_key(&cipher->shannon, cipher->key, sizeof(cipher->key));
|
||||
|
||||
nonce = htobe32(cipher->nonce);
|
||||
shn_nonce(&cipher->shannon, (uint8_t *)&nonce, sizeof(nonce));
|
||||
|
||||
// Decrypt header to get the size, save it in case another pass will be
|
||||
// required
|
||||
shn_decrypt(&cipher->shannon, encrypted, header_len);
|
||||
memcpy(cipher->last_header, encrypted, header_len);
|
||||
|
||||
payload_len = payload_len_get(cipher->last_header);
|
||||
|
||||
// crypto_log("Payload len is %zu\n", payload_len);
|
||||
// crypto_hexdump("Decrypted header\n", encrypted, header_len);
|
||||
}
|
||||
|
||||
// At this point the header is already decrypted, so now decrypt the payload
|
||||
encrypted += header_len;
|
||||
encrypted_len -= header_len + sizeof(mac);
|
||||
|
||||
// Not enough data for decrypting the entire packet
|
||||
if (payload_len > encrypted_len)
|
||||
{
|
||||
crypto_log("Waiting for %zu payload bytes, have %zu\n", payload_len, encrypted_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
shn_decrypt(&cipher->shannon, encrypted, payload_len);
|
||||
|
||||
// crypto_hexdump("Decrypted payload\n", encrypted, payload_len);
|
||||
|
||||
shn_finish(&cipher->shannon, mac, sizeof(mac));
|
||||
// crypto_hexdump("mac in\n", encrypted + payload_len, sizeof(mac));
|
||||
// crypto_hexdump("mac our\n", mac, sizeof(mac));
|
||||
if (memcmp(mac, encrypted + payload_len, sizeof(mac)) != 0)
|
||||
{
|
||||
crypto_log("MAC VALIDATION FAILED\n"); // TODO
|
||||
memset(cipher->last_header, 0, header_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cipher->nonce++;
|
||||
memset(cipher->last_header, 0, header_len);
|
||||
|
||||
return header_len + payload_len + sizeof(mac);
|
||||
}
|
||||
|
||||
void
|
||||
crypto_aes_free(struct crypto_aes_cipher *cipher)
|
||||
{
|
||||
if (!cipher || !cipher->aes)
|
||||
return;
|
||||
|
||||
gcry_cipher_close(cipher->aes);
|
||||
}
|
||||
|
||||
int
|
||||
crypto_aes_new(struct crypto_aes_cipher *cipher, uint8_t *key, size_t key_len, uint8_t *iv, size_t iv_len, const char **errmsg)
|
||||
{
|
||||
gcry_error_t err;
|
||||
|
||||
err = gcry_cipher_open(&cipher->aes, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0);
|
||||
if (err)
|
||||
{
|
||||
*errmsg = "Error initialising AES 128 CTR decryption";
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = gcry_cipher_setkey(cipher->aes, key, key_len);
|
||||
if (err)
|
||||
{
|
||||
*errmsg = "Could not set key for AES 128 CTR";
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = gcry_cipher_setctr(cipher->aes, iv, iv_len);
|
||||
if (err)
|
||||
{
|
||||
*errmsg = "Could not set iv for AES 128 CTR";
|
||||
goto error;
|
||||
}
|
||||
|
||||
memcpy(cipher->aes_iv, iv, iv_len);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
crypto_aes_free(cipher);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
crypto_aes_seek(struct crypto_aes_cipher *cipher, size_t seek, const char **errmsg)
|
||||
{
|
||||
gcry_error_t err;
|
||||
uint64_t be64;
|
||||
uint64_t ctr;
|
||||
uint8_t iv[16];
|
||||
size_t iv_len;
|
||||
size_t num_blocks;
|
||||
size_t offset;
|
||||
|
||||
iv_len = gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128);
|
||||
|
||||
assert(iv_len == sizeof(iv));
|
||||
|
||||
memcpy(iv, cipher->aes_iv, iv_len);
|
||||
num_blocks = seek / iv_len;
|
||||
offset = seek % iv_len;
|
||||
|
||||
// Advance the block counter
|
||||
memcpy(&be64, iv + iv_len / 2, iv_len / 2);
|
||||
ctr = be64toh(be64);
|
||||
ctr += num_blocks;
|
||||
be64 = htobe64(ctr);
|
||||
memcpy(iv + iv_len / 2, &be64, iv_len / 2);
|
||||
|
||||
err = gcry_cipher_setctr(cipher->aes, iv, iv_len);
|
||||
if (err)
|
||||
{
|
||||
*errmsg = "Could not set iv for AES 128 CTR";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Advance if the seek is into a block. iv is used because we have it already,
|
||||
// it could be any buffer as long as it big enough
|
||||
err = gcry_cipher_decrypt(cipher->aes, iv, offset, NULL, 0);
|
||||
if (err)
|
||||
{
|
||||
*errmsg = "Error CTR offset while seeking";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
crypto_aes_decrypt(uint8_t *encrypted, size_t encrypted_len, struct crypto_aes_cipher *cipher, const char **errmsg)
|
||||
{
|
||||
gcry_error_t err;
|
||||
|
||||
err = gcry_cipher_decrypt(cipher->aes, encrypted, encrypted_len, NULL, 0);
|
||||
if (err)
|
||||
{
|
||||
*errmsg = "Error CTR decrypting";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned char
|
||||
crypto_base62_digit(char c)
|
||||
{
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
else if (islower(c))
|
||||
return c - 'a' + 10;
|
||||
else if (isupper(c))
|
||||
return c - 'A' + 10 + 26;
|
||||
else
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
// base 62 to bin: 4gtj0ZuMWRw8WioT9SXsC2 -> 8c283882b29346829b8d021f52f5c2ce
|
||||
// 00AdHZ94Jb7oVdHVJmJsIU -> 004f421c7e934635aaf778180a8fd068
|
||||
// (note that the function prefixes with zeroes)
|
||||
int
|
||||
crypto_base62_to_bin(uint8_t *out, size_t out_len, const char *in)
|
||||
{
|
||||
uint8_t u8;
|
||||
bnum n;
|
||||
bnum base;
|
||||
bnum digit;
|
||||
const char *ptr;
|
||||
size_t len;
|
||||
|
||||
u8 = 62;
|
||||
bnum_bin2bn(base, &u8, sizeof(u8));
|
||||
bnum_new(n);
|
||||
|
||||
for (ptr = in; *ptr; ptr++)
|
||||
{
|
||||
// n = 62 * n + base62_digit(*p);
|
||||
bnum_mul(n, n, base);
|
||||
u8 = crypto_base62_digit(*ptr);
|
||||
|
||||
// Heavy on alloc's, but means we can use bnum compability wrapper
|
||||
bnum_bin2bn(digit, &u8, sizeof(u8));
|
||||
bnum_add(n, n, digit);
|
||||
bnum_free(digit);
|
||||
}
|
||||
|
||||
len = bnum_num_bytes(n);
|
||||
if (len > out_len)
|
||||
goto error;
|
||||
|
||||
memset(out, 0, out_len - len);
|
||||
bnum_bn2bin(n, out + out_len - len, len);
|
||||
|
||||
bnum_free(n);
|
||||
bnum_free(base);
|
||||
|
||||
return (int)out_len;
|
||||
|
||||
error:
|
||||
bnum_free(n);
|
||||
bnum_free(base);
|
||||
return -1;
|
||||
}
|
||||
75
src/inputs/librespot-c/src/crypto.h
Normal file
75
src/inputs/librespot-c/src/crypto.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef __CRYPTO_H__
|
||||
#define __CRYPTO_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <gcrypt.h>
|
||||
|
||||
#include "shannon/Shannon.h"
|
||||
|
||||
struct crypto_cipher
|
||||
{
|
||||
shn_ctx shannon;
|
||||
uint8_t key[32];
|
||||
uint32_t nonce;
|
||||
uint8_t last_header[3]; // uint8 cmd and uint16 BE size
|
||||
|
||||
void (*logmsg)(const char *fmt, ...);
|
||||
};
|
||||
|
||||
struct crypto_aes_cipher
|
||||
{
|
||||
gcry_cipher_hd_t aes;
|
||||
uint8_t key[16];
|
||||
uint8_t aes_iv[16];
|
||||
};
|
||||
|
||||
struct crypto_keys
|
||||
{
|
||||
uint8_t private_key[96];
|
||||
uint8_t public_key[96];
|
||||
|
||||
uint8_t *shared_secret;
|
||||
size_t shared_secret_len;
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
crypto_shared_secret(uint8_t **shared_secret_bytes, size_t *shared_secret_bytes_len,
|
||||
uint8_t *private_key_bytes, size_t private_key_bytes_len,
|
||||
uint8_t *server_key_bytes, size_t server_key_bytes_len);
|
||||
|
||||
int
|
||||
crypto_challenge(uint8_t **challenge, size_t *challenge_len,
|
||||
uint8_t *send_key, size_t send_key_len,
|
||||
uint8_t *recv_key, size_t recv_key_len,
|
||||
uint8_t *packets, size_t packets_len,
|
||||
uint8_t *shared_secret, size_t shared_secret_len);
|
||||
|
||||
int
|
||||
crypto_keys_set(struct crypto_keys *keys);
|
||||
|
||||
ssize_t
|
||||
crypto_encrypt(uint8_t *buf, size_t buf_len, size_t plain_len, struct crypto_cipher *cipher);
|
||||
|
||||
ssize_t
|
||||
crypto_decrypt(uint8_t *encrypted, size_t encrypted_len, struct crypto_cipher *cipher);
|
||||
|
||||
|
||||
void
|
||||
crypto_aes_free(struct crypto_aes_cipher *cipher);
|
||||
|
||||
int
|
||||
crypto_aes_new(struct crypto_aes_cipher *cipher, uint8_t *key, size_t key_len, uint8_t *iv, size_t iv_len, const char **errmsg);
|
||||
|
||||
int
|
||||
crypto_aes_seek(struct crypto_aes_cipher *cipher, size_t seek, const char **errmsg);
|
||||
|
||||
int
|
||||
crypto_aes_decrypt(uint8_t *encrypted, size_t encrypted_len, struct crypto_aes_cipher *cipher, const char **errmsg);
|
||||
|
||||
|
||||
int
|
||||
crypto_base62_to_bin(uint8_t *out, size_t out_len, const char *in);
|
||||
|
||||
#endif /* __CRYPTO_H__ */
|
||||
343
src/inputs/librespot-c/src/librespot-c-internal.h
Normal file
343
src/inputs/librespot-c/src/librespot-c-internal.h
Normal file
@@ -0,0 +1,343 @@
|
||||
#ifndef __LIBRESPOT_C_INTERNAL_H__
|
||||
#define __LIBRESPOT_C_INTERNAL_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/buffer.h>
|
||||
|
||||
#ifdef HAVE_ENDIAN_H
|
||||
# include <endian.h>
|
||||
#elif defined(HAVE_SYS_ENDIAN_H)
|
||||
# include <sys/endian.h>
|
||||
#elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#define htobe16(x) OSSwapHostToBigInt16(x)
|
||||
#define be16toh(x) OSSwapBigToHostInt16(x)
|
||||
#define htobe32(x) OSSwapHostToBigInt32(x)
|
||||
#define be32toh(x) OSSwapBigToHostInt32(x)
|
||||
#define htobe64(x) OSSwapHostToBigInt64(x)
|
||||
#define be64toh(x) OSSwapBigToHostInt64(x)
|
||||
#endif
|
||||
|
||||
#include "librespot-c.h"
|
||||
#include "crypto.h"
|
||||
|
||||
#include "proto/keyexchange.pb-c.h"
|
||||
#include "proto/authentication.pb-c.h"
|
||||
#include "proto/mercury.pb-c.h"
|
||||
#include "proto/metadata.pb-c.h"
|
||||
|
||||
#define SP_AP_RESOLVE_URL "https://APResolve.spotify.com/"
|
||||
#define SP_AP_RESOLVE_KEY "ap_list"
|
||||
|
||||
// Disconnect from AP after this number of secs idle
|
||||
#define SP_AP_DISCONNECT_SECS 60
|
||||
|
||||
// Max wait for AP to respond
|
||||
#define SP_AP_TIMEOUT_SECS 10
|
||||
|
||||
// If client hasn't requested anything in particular
|
||||
#define SP_BITRATE_DEFAULT SP_BITRATE_320
|
||||
|
||||
// A "mercury" response may contain multiple parts (e.g. multiple tracks), even
|
||||
// though this implenentation currently expects just one.
|
||||
#define SP_MERCURY_MAX_PARTS 32
|
||||
|
||||
// librespot uses /3, but -golang and -java use /4
|
||||
#define SP_MERCURY_URI_TRACK "hm://metadata/4/track/"
|
||||
#define SP_MERCURY_URI_EPISODE "hm://metadata/4/episode/"
|
||||
|
||||
// Special Spotify header that comes before the actual Ogg data
|
||||
#define SP_OGG_HEADER_LEN 167
|
||||
|
||||
// For now we just always use channel 0, expand with more if needed
|
||||
#define SP_DEFAULT_CHANNEL 0
|
||||
|
||||
// Download in chunks of 32768 bytes. The chunks shouldn't be too large because
|
||||
// it makes seeking slow (seeking involves jumping around in the file), but
|
||||
// large enough that the file can be probed from the first chunk.
|
||||
#define SP_CHUNK_LEN_WORDS 1024 * 8
|
||||
|
||||
// Shorthand for error handling
|
||||
#define RETURN_ERROR(r, m) \
|
||||
do { ret = (r); sp_errmsg = (m); goto error; } while(0)
|
||||
|
||||
enum sp_error
|
||||
{
|
||||
SP_OK_OTHER = 3,
|
||||
SP_OK_WAIT = 2,
|
||||
SP_OK_DATA = 1,
|
||||
SP_OK_DONE = 0,
|
||||
SP_ERR_OOM = -1,
|
||||
SP_ERR_INVALID = -2,
|
||||
SP_ERR_DECRYPTION = -3,
|
||||
SP_ERR_WRITE = -4,
|
||||
SP_ERR_NOCONNECTION = -5,
|
||||
SP_ERR_OCCUPIED = -6,
|
||||
SP_ERR_NOSESSION = -7,
|
||||
SP_ERR_LOGINFAILED = -8,
|
||||
SP_ERR_TIMEOUT = -9,
|
||||
};
|
||||
|
||||
enum sp_msg_type
|
||||
{
|
||||
MSG_TYPE_NONE,
|
||||
MSG_TYPE_CLIENT_HELLO,
|
||||
MSG_TYPE_CLIENT_RESPONSE_PLAINTEXT,
|
||||
MSG_TYPE_CLIENT_RESPONSE_ENCRYPTED,
|
||||
MSG_TYPE_PONG,
|
||||
MSG_TYPE_MERCURY_TRACK_GET,
|
||||
MSG_TYPE_MERCURY_EPISODE_GET,
|
||||
MSG_TYPE_AUDIO_KEY_GET,
|
||||
MSG_TYPE_CHUNK_REQUEST,
|
||||
};
|
||||
|
||||
enum sp_media_type
|
||||
{
|
||||
SP_MEDIA_UNKNOWN,
|
||||
SP_MEDIA_TRACK,
|
||||
SP_MEDIA_EPISODE,
|
||||
};
|
||||
|
||||
// From librespot-golang
|
||||
enum sp_cmd_type
|
||||
{
|
||||
CmdNone = 0x00,
|
||||
CmdSecretBlock = 0x02,
|
||||
CmdPing = 0x04,
|
||||
CmdStreamChunk = 0x08,
|
||||
|
||||
CmdStreamChunkRes = 0x09,
|
||||
CmdChannelError = 0x0a,
|
||||
CmdChannelAbort = 0x0b,
|
||||
CmdRequestKey = 0x0c,
|
||||
CmdAesKey = 0x0d,
|
||||
CmdAesKeyError = 0x0e,
|
||||
|
||||
CmdImage = 0x19,
|
||||
CmdCountryCode = 0x1b,
|
||||
|
||||
CmdPong = 0x49,
|
||||
CmdPongAck = 0x4a,
|
||||
CmdPause = 0x4b,
|
||||
|
||||
CmdProductInfo = 0x50,
|
||||
CmdLegacyWelcome = 0x69,
|
||||
|
||||
CmdLicenseVersion = 0x76,
|
||||
CmdLogin = 0xab,
|
||||
CmdAPWelcome = 0xac,
|
||||
CmdAuthFailure = 0xad,
|
||||
|
||||
CmdMercuryReq = 0xb2,
|
||||
CmdMercurySub = 0xb3,
|
||||
CmdMercuryUnsub = 0xb4,
|
||||
};
|
||||
|
||||
struct sp_cmdargs
|
||||
{
|
||||
struct sp_session *session;
|
||||
struct sp_credentials *credentials;
|
||||
struct sp_metadata *metadata;
|
||||
const char *username;
|
||||
const char *password;
|
||||
uint8_t *stored_cred;
|
||||
size_t stored_cred_len;
|
||||
const char *token;
|
||||
const char *path;
|
||||
int fd_read;
|
||||
int fd_write;
|
||||
size_t seek_pos;
|
||||
enum sp_bitrates bitrate;
|
||||
|
||||
sp_progress_cb progress_cb;
|
||||
void *cb_arg;
|
||||
};
|
||||
|
||||
struct sp_conn_callbacks
|
||||
{
|
||||
struct event_base *evbase;
|
||||
|
||||
event_callback_fn response_cb;
|
||||
event_callback_fn timeout_cb;
|
||||
};
|
||||
|
||||
struct sp_message
|
||||
{
|
||||
enum sp_msg_type type;
|
||||
enum sp_cmd_type cmd;
|
||||
|
||||
bool encrypt;
|
||||
bool add_version_header;
|
||||
|
||||
enum sp_msg_type type_next;
|
||||
enum sp_msg_type type_queued;
|
||||
|
||||
int (*response_handler)(uint8_t *msg, size_t msg_len, struct sp_session *session);
|
||||
|
||||
ssize_t len;
|
||||
uint8_t data[4096];
|
||||
};
|
||||
|
||||
struct sp_connection
|
||||
{
|
||||
bool is_connected;
|
||||
bool is_encrypted;
|
||||
|
||||
// Resolved access point
|
||||
char *ap_address;
|
||||
unsigned short ap_port;
|
||||
|
||||
// Where we receive data from Spotify
|
||||
int response_fd;
|
||||
struct event *response_ev;
|
||||
|
||||
// Connection timers
|
||||
struct event *idle_ev;
|
||||
struct event *timeout_ev;
|
||||
|
||||
// Holds incoming data
|
||||
struct evbuffer *incoming;
|
||||
|
||||
// Buffer holding client hello and ap response, since they are needed for
|
||||
// MAC calculation
|
||||
bool handshake_completed;
|
||||
struct evbuffer *handshake_packets;
|
||||
|
||||
struct crypto_keys keys;
|
||||
struct crypto_cipher encrypt;
|
||||
struct crypto_cipher decrypt;
|
||||
};
|
||||
|
||||
struct sp_mercury
|
||||
{
|
||||
char *uri;
|
||||
char *method;
|
||||
char *content_type;
|
||||
|
||||
uint64_t seq;
|
||||
|
||||
uint16_t parts_num;
|
||||
struct sp_mercury_parts
|
||||
{
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
|
||||
Track *track;
|
||||
} parts[SP_MERCURY_MAX_PARTS];
|
||||
};
|
||||
|
||||
struct sp_file
|
||||
{
|
||||
uint8_t id[20];
|
||||
|
||||
char *path; // The Spotify URI, e.g. spotify:episode:3KRjRyqv5ou5SilNMYBR4E
|
||||
uint8_t media_id[16]; // Decoded value of the URIs base62
|
||||
enum sp_media_type media_type; // track or episode from URI
|
||||
|
||||
uint8_t key[16];
|
||||
|
||||
uint16_t channel_id;
|
||||
|
||||
// Length and download progress
|
||||
size_t len_words; // Length of file in words (32 bit)
|
||||
size_t offset_words;
|
||||
size_t received_words;
|
||||
bool end_of_file;
|
||||
bool end_of_chunk;
|
||||
bool open;
|
||||
|
||||
struct crypto_aes_cipher decrypt;
|
||||
};
|
||||
|
||||
struct sp_channel_header
|
||||
{
|
||||
uint16_t len;
|
||||
uint8_t id;
|
||||
uint8_t *data;
|
||||
size_t data_len;
|
||||
};
|
||||
|
||||
struct sp_channel_body
|
||||
{
|
||||
uint8_t *data;
|
||||
size_t data_len;
|
||||
};
|
||||
|
||||
struct sp_channel
|
||||
{
|
||||
int id;
|
||||
|
||||
bool is_allocated;
|
||||
bool is_writing;
|
||||
bool is_data_mode;
|
||||
bool is_spotify_header_received;
|
||||
size_t seek_pos;
|
||||
size_t seek_align;
|
||||
|
||||
// pipe where we write audio data
|
||||
int audio_fd[2];
|
||||
// Triggers when fd is writable
|
||||
struct event *audio_write_ev;
|
||||
// Storage of audio until it can be written to the pipe
|
||||
struct evbuffer *audio_buf;
|
||||
// How much we have written to the fd (only used for debug)
|
||||
size_t audio_written_len;
|
||||
|
||||
struct sp_file file;
|
||||
|
||||
// Latest header and body received
|
||||
struct sp_channel_header header;
|
||||
struct sp_channel_body body;
|
||||
|
||||
// Callbacks made during playback
|
||||
sp_progress_cb progress_cb;
|
||||
void *cb_arg;
|
||||
};
|
||||
|
||||
// Linked list of sessions
|
||||
struct sp_session
|
||||
{
|
||||
struct sp_connection conn;
|
||||
|
||||
bool is_logged_in;
|
||||
struct sp_credentials credentials;
|
||||
char country[3]; // Incl null term
|
||||
|
||||
enum sp_bitrates bitrate_preferred;
|
||||
|
||||
struct sp_channel channels[8];
|
||||
|
||||
// Points to the channel that is streaming, and via this information about
|
||||
// the current track is also available
|
||||
struct sp_channel *now_streaming_channel;
|
||||
|
||||
// Go to next step in a request sequence
|
||||
struct event *continue_ev;
|
||||
|
||||
// Current (or last) message being processed
|
||||
enum sp_msg_type msg_type_queued;
|
||||
enum sp_msg_type msg_type_next;
|
||||
int (*response_handler)(uint8_t *, size_t, struct sp_session *);
|
||||
|
||||
struct sp_session *next;
|
||||
};
|
||||
|
||||
struct sp_err_map
|
||||
{
|
||||
ErrorCode errorcode;
|
||||
const char *errmsg;
|
||||
};
|
||||
|
||||
extern struct sp_callbacks sp_cb;
|
||||
extern struct sp_sysinfo sp_sysinfo;
|
||||
extern const char *sp_errmsg;
|
||||
|
||||
#endif // __LIBRESPOT_C_INTERNAL_H__
|
||||
977
src/inputs/librespot-c/src/librespot-c.c
Normal file
977
src/inputs/librespot-c/src/librespot-c.c
Normal file
@@ -0,0 +1,977 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Illustration of the general flow, where receive and writing the result are async
|
||||
operations. For some commands, e.g. open and seek, the entire sequence is
|
||||
encapsulated in a sync command, which doesn't return until final "done, error or
|
||||
timeout". The command play is async, so all "done/error/timeout" is returned via
|
||||
callbacks. Also, play will loop the flow, i.e. after writing a chunk of data it
|
||||
will go back and ask for the next chunk of data from Spotify.
|
||||
|
||||
In some cases there is no result to write, or no reponse expected, but then the
|
||||
events for proceeding are activated directly.
|
||||
|
||||
|---next----*------------next-------------*----------next----------*
|
||||
v | | |
|
||||
----------> start/send ------------------> recv ----------------> write result
|
||||
^ | ^ | ^ |
|
||||
|---reconnect---* |------wait------* |------wait------*
|
||||
| | |
|
||||
v v v
|
||||
done/error done/error/timeout done/error
|
||||
|
||||
"next": on success, continue with next command
|
||||
"wait": waiting for more data or for write to become possible
|
||||
"timeout": receive or write took too long to complete
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "librespot-c-internal.h"
|
||||
#include "commands.h"
|
||||
#include "connection.h"
|
||||
#include "channel.h"
|
||||
|
||||
/* TODO list
|
||||
|
||||
- protect against DOS
|
||||
*/
|
||||
|
||||
|
||||
/* -------------------------------- Globals --------------------------------- */
|
||||
|
||||
// Shared
|
||||
struct sp_callbacks sp_cb;
|
||||
struct sp_sysinfo sp_sysinfo;
|
||||
const char *sp_errmsg;
|
||||
|
||||
static struct sp_session *sp_sessions;
|
||||
|
||||
static bool sp_initialized;
|
||||
|
||||
static pthread_t sp_tid;
|
||||
static struct event_base *sp_evbase;
|
||||
static struct commands_base *sp_cmdbase;
|
||||
|
||||
static struct timeval sp_response_timeout_tv = { SP_AP_TIMEOUT_SECS, 0 };
|
||||
|
||||
|
||||
// Forwards
|
||||
static int
|
||||
request_make(enum sp_msg_type type, struct sp_session *session);
|
||||
|
||||
|
||||
/* -------------------------------- Session --------------------------------- */
|
||||
|
||||
static void
|
||||
session_free(struct sp_session *session)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
channel_free_all(session);
|
||||
|
||||
ap_disconnect(&session->conn);
|
||||
|
||||
event_free(session->continue_ev);
|
||||
free(session);
|
||||
}
|
||||
|
||||
static void
|
||||
session_cleanup(struct sp_session *session)
|
||||
{
|
||||
struct sp_session *s;
|
||||
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
if (session == sp_sessions)
|
||||
sp_sessions = session->next;
|
||||
else
|
||||
{
|
||||
for (s = sp_sessions; s && (s->next != session); s = s->next)
|
||||
; /* EMPTY */
|
||||
|
||||
if (s)
|
||||
s->next = session->next;
|
||||
}
|
||||
|
||||
session_free(session);
|
||||
}
|
||||
|
||||
static int
|
||||
session_new(struct sp_session **out, struct sp_cmdargs *cmdargs, event_callback_fn cb)
|
||||
{
|
||||
struct sp_session *session;
|
||||
int ret;
|
||||
|
||||
session = calloc(1, sizeof(struct sp_session));
|
||||
if (!session)
|
||||
RETURN_ERROR(SP_ERR_OOM, "Out of memory creating session");
|
||||
|
||||
session->continue_ev = evtimer_new(sp_evbase, cb, session);
|
||||
if (!session->continue_ev)
|
||||
RETURN_ERROR(SP_ERR_OOM, "Out of memory creating session event");
|
||||
|
||||
snprintf(session->credentials.username, sizeof(session->credentials.username), "%s", cmdargs->username);
|
||||
|
||||
if (cmdargs->stored_cred)
|
||||
{
|
||||
if (cmdargs->stored_cred_len > sizeof(session->credentials.stored_cred))
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Invalid stored credential");
|
||||
|
||||
session->credentials.stored_cred_len = cmdargs->stored_cred_len;
|
||||
memcpy(session->credentials.stored_cred, cmdargs->stored_cred, session->credentials.stored_cred_len);
|
||||
}
|
||||
else if (cmdargs->token)
|
||||
{
|
||||
if (strlen(cmdargs->token) > sizeof(session->credentials.token))
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Invalid token");
|
||||
|
||||
session->credentials.token_len = strlen(cmdargs->token);
|
||||
memcpy(session->credentials.token, cmdargs->token, session->credentials.token_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(session->credentials.password, sizeof(session->credentials.password), "%s", cmdargs->password);
|
||||
}
|
||||
|
||||
session->bitrate_preferred = SP_BITRATE_DEFAULT;
|
||||
|
||||
// Add to linked list
|
||||
session->next = sp_sessions;
|
||||
sp_sessions = session;
|
||||
|
||||
*out = session;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
session_free(session);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
session_check(struct sp_session *session)
|
||||
{
|
||||
struct sp_session *s;
|
||||
|
||||
for (s = sp_sessions; s; s = s->next)
|
||||
{
|
||||
if (s == session)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static struct sp_session *
|
||||
session_find_by_fd(int fd)
|
||||
{
|
||||
struct sp_session *s;
|
||||
|
||||
for (s = sp_sessions; s; s = s->next)
|
||||
{
|
||||
if (s->now_streaming_channel && s->now_streaming_channel->audio_fd[0] == fd)
|
||||
return s;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
session_return(struct sp_session *session, enum sp_error err)
|
||||
{
|
||||
struct sp_channel *channel = session->now_streaming_channel;
|
||||
int ret;
|
||||
|
||||
ret = commands_exec_returnvalue(sp_cmdbase);
|
||||
if (ret == 0) // Here we are async, i.e. no pending command
|
||||
{
|
||||
// track_write() completed, close the write end which means reader will
|
||||
// get an EOF
|
||||
if (channel && channel->is_writing && err == SP_OK_DONE)
|
||||
channel_stop(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
commands_exec_end(sp_cmdbase, err);
|
||||
}
|
||||
|
||||
// Rolls back from an error situation. If it is a failed login then the session
|
||||
// will be closed, but if it just a connection timeout we keep the session, but
|
||||
// drop the ongoing download.
|
||||
static void
|
||||
session_error(struct sp_session *session, enum sp_error err)
|
||||
{
|
||||
struct sp_channel *channel = session->now_streaming_channel;
|
||||
|
||||
sp_cb.logmsg("Session error: %d\n", err);
|
||||
|
||||
session_return(session, err);
|
||||
|
||||
if (!session->is_logged_in)
|
||||
{
|
||||
session_cleanup(session);
|
||||
return;
|
||||
}
|
||||
|
||||
channel_free(channel);
|
||||
session->now_streaming_channel = NULL;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------ Main sequence control --------------------------- */
|
||||
|
||||
// This callback must determine if a new request should be made, or if we are
|
||||
// done and should return to caller
|
||||
static void
|
||||
continue_cb(int fd, short what, void *arg)
|
||||
{
|
||||
struct sp_session *session = arg;
|
||||
enum sp_msg_type type = MSG_TYPE_NONE;
|
||||
int ret;
|
||||
|
||||
// type_next has priority, since this is what we use to chain a sequence, e.g.
|
||||
// the handshake sequence. type_queued is what comes after, e.g. first a
|
||||
// handshake (type_next) and then a chunk request (type_queued)
|
||||
if (session->msg_type_next != MSG_TYPE_NONE)
|
||||
{
|
||||
// sp_cb.logmsg(">>> msg_next >>>\n");
|
||||
|
||||
type = session->msg_type_next;
|
||||
session->msg_type_next = MSG_TYPE_NONE;
|
||||
}
|
||||
else if (session->msg_type_queued != MSG_TYPE_NONE)
|
||||
{
|
||||
// sp_cb.logmsg(">>> msg_queued >>>\n");
|
||||
|
||||
type = session->msg_type_queued;
|
||||
session->msg_type_queued = MSG_TYPE_NONE;
|
||||
}
|
||||
|
||||
if (type != MSG_TYPE_NONE)
|
||||
{
|
||||
ret = request_make(type, session);
|
||||
if (ret < 0)
|
||||
session_error(session, ret);
|
||||
}
|
||||
else
|
||||
session_return(session, SP_OK_DONE); // All done, yay!
|
||||
}
|
||||
|
||||
// This callback is triggered by response_cb when the message response handler
|
||||
// said that there was data to write. If not all data can be written in one pass
|
||||
// it will re-add the event.
|
||||
static void
|
||||
audio_write_cb(int fd, short what, void *arg)
|
||||
{
|
||||
struct sp_session *session = arg;
|
||||
struct sp_channel *channel = session->now_streaming_channel;
|
||||
int ret;
|
||||
|
||||
if (!channel)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Write result request, but not streaming right now");
|
||||
|
||||
ret = channel_data_write(channel);
|
||||
switch (ret)
|
||||
{
|
||||
case SP_OK_WAIT:
|
||||
event_add(channel->audio_write_ev, NULL);
|
||||
break;
|
||||
case SP_OK_DONE:
|
||||
event_active(session->continue_ev, 0, 0);
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
session_error(session, ret);
|
||||
}
|
||||
|
||||
static void
|
||||
timeout_cb(int fd, short what, void *arg)
|
||||
{
|
||||
struct sp_session *session = arg;
|
||||
|
||||
sp_errmsg = "Timeout waiting for Spotify response";
|
||||
|
||||
session_error(session, SP_ERR_TIMEOUT);
|
||||
}
|
||||
|
||||
static void
|
||||
response_cb(int fd, short what, void *arg)
|
||||
{
|
||||
struct sp_session *session = arg;
|
||||
struct sp_connection *conn = &session->conn;
|
||||
struct sp_channel *channel = session->now_streaming_channel;
|
||||
int ret;
|
||||
|
||||
if (what == EV_READ)
|
||||
{
|
||||
ret = evbuffer_read(conn->incoming, fd, -1);
|
||||
if (ret == 0)
|
||||
RETURN_ERROR(SP_ERR_NOCONNECTION, "The access point disconnected");
|
||||
else if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_NOCONNECTION, "Connection to Spotify returned an error");
|
||||
|
||||
// sp_cb.logmsg("Received data len %d\n", ret);
|
||||
}
|
||||
|
||||
ret = response_read(session);
|
||||
switch (ret)
|
||||
{
|
||||
case SP_OK_WAIT: // Incomplete, wait for more data
|
||||
break;
|
||||
case SP_OK_DATA:
|
||||
if (channel->is_writing && !channel->file.end_of_file)
|
||||
session->msg_type_next = MSG_TYPE_CHUNK_REQUEST;
|
||||
if (channel->progress_cb)
|
||||
channel->progress_cb(channel->audio_fd[0], channel->cb_arg, 4 * channel->file.received_words - SP_OGG_HEADER_LEN, 4 * channel->file.len_words - SP_OGG_HEADER_LEN);
|
||||
|
||||
event_del(conn->timeout_ev);
|
||||
event_add(channel->audio_write_ev, NULL);
|
||||
break;
|
||||
case SP_OK_DONE: // Got the response we expected, but possibly more to process
|
||||
if (evbuffer_get_length(conn->incoming) > 0)
|
||||
event_active(conn->response_ev, 0, 0);
|
||||
|
||||
event_del(conn->timeout_ev);
|
||||
event_active(session->continue_ev, 0, 0);
|
||||
break;
|
||||
case SP_OK_OTHER: // Not the response we were waiting for, check for other
|
||||
if (evbuffer_get_length(conn->incoming) > 0)
|
||||
event_active(conn->response_ev, 0, 0);
|
||||
break;
|
||||
default:
|
||||
event_del(conn->timeout_ev);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
session_error(session, ret);
|
||||
}
|
||||
|
||||
static int
|
||||
relogin(enum sp_msg_type type, struct sp_session *session)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (session->msg_type_queued != MSG_TYPE_NONE)
|
||||
RETURN_ERROR(SP_ERR_NOCONNECTION, "Cannot send message, another request is waiting for handshake");
|
||||
|
||||
ret = request_make(MSG_TYPE_CLIENT_HELLO, session);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(ret, sp_errmsg);
|
||||
|
||||
// In case we lost connection to the AP we have to make a new handshake for
|
||||
// the non-handshake message types. So queue the message until the handshake
|
||||
// is complete.
|
||||
session->msg_type_queued = type;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
request_make(enum sp_msg_type type, struct sp_session *session)
|
||||
{
|
||||
struct sp_message msg;
|
||||
struct sp_connection *conn = &session->conn;
|
||||
struct sp_conn_callbacks cb = { sp_evbase, response_cb, timeout_cb };
|
||||
int ret;
|
||||
|
||||
// Make sure the connection is in a state suitable for sending this message
|
||||
ret = ap_connect(type, &cb, session);
|
||||
if (ret == SP_OK_WAIT)
|
||||
return relogin(type, session); // Can't proceed right now, the handshake needs to complete first
|
||||
else if (ret < 0)
|
||||
RETURN_ERROR(ret, sp_errmsg);
|
||||
|
||||
ret = msg_make(&msg, type, session);
|
||||
if (type == MSG_TYPE_CLIENT_RESPONSE_ENCRYPTED)
|
||||
memset(session->credentials.password, 0, sizeof(session->credentials.password));
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Error constructing message to Spotify");
|
||||
|
||||
if (msg.encrypt)
|
||||
conn->is_encrypted = true;
|
||||
|
||||
ret = msg_send(&msg, conn);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(ret, sp_errmsg);
|
||||
|
||||
// Only start timeout timer if a response is expected, otherwise go straight
|
||||
// to next message
|
||||
if (msg.response_handler)
|
||||
event_add(conn->timeout_ev, &sp_response_timeout_tv);
|
||||
else
|
||||
event_active(session->continue_ev, 0, 0);
|
||||
|
||||
session->msg_type_next = msg.type_next;
|
||||
session->response_handler = msg.response_handler;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------- Implementation ----------------------------- */
|
||||
|
||||
// This command is async
|
||||
static enum command_state
|
||||
track_write(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session;
|
||||
struct sp_channel *channel;
|
||||
int ret;
|
||||
|
||||
*retval = 0;
|
||||
|
||||
session = session_find_by_fd(cmdargs->fd_read);
|
||||
if (!session)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Cannot play track, no valid session found");
|
||||
|
||||
channel = session->now_streaming_channel;
|
||||
if (!channel || !channel->is_allocated)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "No active channel to play, has track been opened?");
|
||||
|
||||
channel_play(channel);
|
||||
|
||||
ret = request_make(MSG_TYPE_CHUNK_REQUEST, session);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_NOCONNECTION, "Could not send request for audio chunk");
|
||||
|
||||
channel->progress_cb = cmdargs->progress_cb;
|
||||
channel->cb_arg = cmdargs->cb_arg;
|
||||
|
||||
return COMMAND_END;
|
||||
|
||||
error:
|
||||
sp_cb.logmsg("Error %d: %s", ret, sp_errmsg);
|
||||
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
track_pause(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session;
|
||||
struct sp_channel *channel;
|
||||
int ret;
|
||||
|
||||
session = session_find_by_fd(cmdargs->fd_read);
|
||||
if (!session)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Cannot pause track, no valid session found");
|
||||
|
||||
channel = session->now_streaming_channel;
|
||||
if (!channel || !channel->is_allocated)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "No active channel to pause, has track been opened?");
|
||||
|
||||
// If we are playing we are in the process of downloading a chunk, and in that
|
||||
// case we need that to complete before doing anything else with the channel,
|
||||
// e.g. reset it as track_close() does.
|
||||
if (!channel->is_writing)
|
||||
{
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
channel_pause(channel);
|
||||
|
||||
*retval = 1;
|
||||
return COMMAND_PENDING;
|
||||
|
||||
error:
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
track_seek(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session;
|
||||
struct sp_channel *channel;
|
||||
int ret;
|
||||
|
||||
session = session_find_by_fd(cmdargs->fd_read);
|
||||
if (!session)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Cannot seek, no valid session found");
|
||||
|
||||
channel = session->now_streaming_channel;
|
||||
if (!channel || !channel->is_allocated)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "No active channel to seek, has track been opened?");
|
||||
else if (channel->is_writing)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Seeking during playback not currently supported");
|
||||
|
||||
// This operation is not safe during chunk downloading because it changes the
|
||||
// AES decryptor to match the new position. It also flushes the pipe.
|
||||
channel_seek(channel, cmdargs->seek_pos);
|
||||
|
||||
ret = request_make(MSG_TYPE_CHUNK_REQUEST, session);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_NOCONNECTION, "Could not send track seek request");
|
||||
|
||||
*retval = 1;
|
||||
return COMMAND_PENDING;
|
||||
|
||||
error:
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
track_close(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session;
|
||||
int ret;
|
||||
|
||||
session = session_find_by_fd(cmdargs->fd_read);
|
||||
if (!session)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Cannot close track, no valid session found");
|
||||
|
||||
channel_free(session->now_streaming_channel);
|
||||
session->now_streaming_channel = NULL;
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
|
||||
error:
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
media_open(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session = cmdargs->session;
|
||||
struct sp_channel *channel = NULL;
|
||||
enum sp_msg_type type;
|
||||
int ret;
|
||||
|
||||
ret = session_check(session);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Cannot open media, session is invalid");
|
||||
|
||||
if (session->now_streaming_channel)
|
||||
RETURN_ERROR(SP_ERR_OCCUPIED, "Already getting media");
|
||||
|
||||
ret = channel_new(&channel, session, cmdargs->path, sp_evbase, audio_write_cb);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_OOM, "Could not setup a channel");
|
||||
|
||||
cmdargs->fd_read = channel->audio_fd[0];
|
||||
|
||||
// Must be set before calling request_make() because this info is needed for
|
||||
// making the request
|
||||
session->now_streaming_channel = channel;
|
||||
|
||||
if (channel->file.media_type == SP_MEDIA_TRACK)
|
||||
type = MSG_TYPE_MERCURY_TRACK_GET;
|
||||
else if (channel->file.media_type == SP_MEDIA_EPISODE)
|
||||
type = MSG_TYPE_MERCURY_EPISODE_GET;
|
||||
else
|
||||
RETURN_ERROR(SP_ERR_INVALID, "Unknown media type in Spotify path");
|
||||
|
||||
// Kicks of a sequence where we first get file info, then get the AES key and
|
||||
// then the first chunk (incl. headers)
|
||||
ret = request_make(type, session);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_NOCONNECTION, "Could not send media request");
|
||||
|
||||
*retval = 1;
|
||||
return COMMAND_PENDING;
|
||||
|
||||
error:
|
||||
if (channel)
|
||||
{
|
||||
session->now_streaming_channel = NULL;
|
||||
channel_free(channel);
|
||||
}
|
||||
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
media_open_bh(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
|
||||
if (*retval == SP_OK_DONE)
|
||||
*retval = cmdargs->fd_read;
|
||||
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
login(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session = NULL;
|
||||
int ret;
|
||||
|
||||
ret = session_new(&session, cmdargs, continue_cb);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
ret = request_make(MSG_TYPE_CLIENT_HELLO, session);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
cmdargs->session = session;
|
||||
|
||||
*retval = 1; // Pending command_exec_sync, i.e. response from Spotify
|
||||
return COMMAND_PENDING;
|
||||
|
||||
error:
|
||||
session_cleanup(session);
|
||||
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
login_bh(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
|
||||
if (*retval == SP_OK_DONE)
|
||||
cmdargs->session->is_logged_in = true;
|
||||
else
|
||||
cmdargs->session = NULL;
|
||||
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
logout(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session = cmdargs->session;
|
||||
int ret;
|
||||
|
||||
ret = session_check(session);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Session has disappeared, cannot logout");
|
||||
|
||||
session_cleanup(session);
|
||||
|
||||
error:
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
metadata_get(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session;
|
||||
struct sp_metadata *metadata = cmdargs->metadata;
|
||||
int ret = 0;
|
||||
|
||||
session = session_find_by_fd(cmdargs->fd_read);
|
||||
if (!session || !session->now_streaming_channel)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Session has disappeared, cannot get metadata");
|
||||
|
||||
memset(metadata, 0, sizeof(struct sp_metadata));
|
||||
metadata->file_len = 4 * session->now_streaming_channel->file.len_words - SP_OGG_HEADER_LEN;;
|
||||
|
||||
error:
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
bitrate_set(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session = cmdargs->session;
|
||||
int ret;
|
||||
|
||||
if (cmdargs->bitrate == SP_BITRATE_ANY)
|
||||
cmdargs->bitrate = SP_BITRATE_DEFAULT;
|
||||
|
||||
ret = session_check(session);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Session has disappeared, cannot set bitrate");
|
||||
|
||||
session->bitrate_preferred = cmdargs->bitrate;
|
||||
|
||||
error:
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
credentials_get(void *arg, int *retval)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs = arg;
|
||||
struct sp_session *session = cmdargs->session;
|
||||
struct sp_credentials *credentials = cmdargs->credentials;
|
||||
int ret;
|
||||
|
||||
ret = session_check(session);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_NOSESSION, "Session has disappeared, cannot get credentials");
|
||||
|
||||
memcpy(credentials, &session->credentials, sizeof(struct sp_credentials));
|
||||
|
||||
error:
|
||||
*retval = ret;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------ Event loop -------------------------------- */
|
||||
|
||||
static void *
|
||||
librespotc(void *arg)
|
||||
{
|
||||
event_base_dispatch(sp_evbase);
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------- API ----------------------------------- */
|
||||
|
||||
int
|
||||
librespotc_open(const char *path, struct sp_session *session)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.session = session;
|
||||
cmdargs.path = path;
|
||||
|
||||
return commands_exec_sync(sp_cmdbase, media_open, media_open_bh, &cmdargs);
|
||||
}
|
||||
|
||||
int
|
||||
librespotc_seek(int fd, size_t pos)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.fd_read = fd;
|
||||
cmdargs.seek_pos = pos;
|
||||
|
||||
return commands_exec_sync(sp_cmdbase, track_seek, NULL, &cmdargs);
|
||||
}
|
||||
|
||||
// Starts writing audio for the caller to read from the file descriptor
|
||||
void
|
||||
librespotc_write(int fd, sp_progress_cb progress_cb, void *cb_arg)
|
||||
{
|
||||
struct sp_cmdargs *cmdargs;
|
||||
|
||||
cmdargs = calloc(1, sizeof(struct sp_cmdargs));
|
||||
|
||||
cmdargs->fd_read = fd;
|
||||
cmdargs->progress_cb = progress_cb;
|
||||
cmdargs->cb_arg = cb_arg;
|
||||
|
||||
commands_exec_async(sp_cmdbase, track_write, cmdargs);
|
||||
}
|
||||
|
||||
int
|
||||
librespotc_close(int fd)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.fd_read = fd;
|
||||
|
||||
return commands_exec_sync(sp_cmdbase, track_pause, track_close, &cmdargs);
|
||||
}
|
||||
|
||||
struct sp_session *
|
||||
librespotc_login_password(const char *username, const char *password)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.username = username;
|
||||
cmdargs.password = password;
|
||||
|
||||
commands_exec_sync(sp_cmdbase, login, login_bh, &cmdargs);
|
||||
|
||||
return cmdargs.session;
|
||||
}
|
||||
|
||||
struct sp_session *
|
||||
librespotc_login_stored_cred(const char *username, uint8_t *stored_cred, size_t stored_cred_len)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.username = username;
|
||||
cmdargs.stored_cred = stored_cred;
|
||||
cmdargs.stored_cred_len = stored_cred_len;
|
||||
|
||||
commands_exec_sync(sp_cmdbase, login, login_bh, &cmdargs);
|
||||
|
||||
return cmdargs.session;
|
||||
}
|
||||
|
||||
struct sp_session *
|
||||
librespotc_login_token(const char *username, const char *token)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.username = username;
|
||||
cmdargs.token = token;
|
||||
|
||||
commands_exec_sync(sp_cmdbase, login, login_bh, &cmdargs);
|
||||
|
||||
return cmdargs.session;
|
||||
}
|
||||
|
||||
int
|
||||
librespotc_logout(struct sp_session *session)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.session = session;
|
||||
|
||||
return commands_exec_sync(sp_cmdbase, logout, NULL, &cmdargs);
|
||||
}
|
||||
|
||||
int
|
||||
librespotc_metadata_get(struct sp_metadata *metadata, int fd)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.metadata = metadata;
|
||||
cmdargs.fd_read = fd;
|
||||
|
||||
return commands_exec_sync(sp_cmdbase, metadata_get, NULL, &cmdargs);
|
||||
}
|
||||
|
||||
int
|
||||
librespotc_bitrate_set(struct sp_session *session, enum sp_bitrates bitrate)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.session = session;
|
||||
cmdargs.bitrate = bitrate;
|
||||
|
||||
return commands_exec_sync(sp_cmdbase, bitrate_set, NULL, &cmdargs);
|
||||
}
|
||||
|
||||
int
|
||||
librespotc_credentials_get(struct sp_credentials *credentials, struct sp_session *session)
|
||||
{
|
||||
struct sp_cmdargs cmdargs = { 0 };
|
||||
|
||||
cmdargs.credentials = credentials;
|
||||
cmdargs.session = session;
|
||||
|
||||
return commands_exec_sync(sp_cmdbase, credentials_get, NULL, &cmdargs);
|
||||
}
|
||||
|
||||
const char *
|
||||
librespotc_last_errmsg(void)
|
||||
{
|
||||
return sp_errmsg ? sp_errmsg : "(no error)";
|
||||
}
|
||||
|
||||
int
|
||||
librespotc_init(struct sp_sysinfo *sysinfo, struct sp_callbacks *callbacks)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (sp_initialized)
|
||||
RETURN_ERROR(SP_ERR_INVALID, "librespot-c already initialized");
|
||||
|
||||
sp_cb = *callbacks;
|
||||
sp_initialized = true;
|
||||
|
||||
memcpy(&sp_sysinfo, sysinfo, sizeof(struct sp_sysinfo));
|
||||
|
||||
sp_evbase = event_base_new();
|
||||
if (!sp_evbase)
|
||||
RETURN_ERROR(SP_ERR_OOM, "event_base_new() failed");
|
||||
|
||||
sp_cmdbase = commands_base_new(sp_evbase, NULL);
|
||||
if (!sp_cmdbase)
|
||||
RETURN_ERROR(SP_ERR_OOM, "commands_base_new() failed");
|
||||
|
||||
ret = pthread_create(&sp_tid, NULL, librespotc, NULL);
|
||||
if (ret < 0)
|
||||
RETURN_ERROR(SP_ERR_OOM, "Could not start thread");
|
||||
|
||||
if (sp_cb.thread_name_set)
|
||||
sp_cb.thread_name_set(sp_tid);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
librespotc_deinit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
librespotc_deinit()
|
||||
{
|
||||
struct sp_session *session;
|
||||
|
||||
if (sp_cmdbase)
|
||||
{
|
||||
commands_base_destroy(sp_cmdbase);
|
||||
sp_cmdbase = NULL;
|
||||
}
|
||||
|
||||
for (session = sp_sessions; sp_sessions; session = sp_sessions)
|
||||
{
|
||||
sp_sessions = session->next;
|
||||
session_free(session);
|
||||
}
|
||||
|
||||
if (sp_tid)
|
||||
{
|
||||
pthread_join(sp_tid, NULL);
|
||||
}
|
||||
|
||||
if (sp_evbase)
|
||||
{
|
||||
event_base_free(sp_evbase);
|
||||
sp_evbase = NULL;
|
||||
}
|
||||
|
||||
sp_initialized = false;
|
||||
memset(&sp_cb, 0, sizeof(struct sp_callbacks));
|
||||
|
||||
return;
|
||||
}
|
||||
51
src/inputs/librespot-c/src/proto/ad-hermes-proxy.proto
Normal file
51
src/inputs/librespot-c/src/proto/ad-hermes-proxy.proto
Normal file
@@ -0,0 +1,51 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message Rule {
|
||||
optional string type = 0x1;
|
||||
optional uint32 times = 0x2;
|
||||
optional uint64 interval = 0x3;
|
||||
}
|
||||
|
||||
message AdRequest {
|
||||
optional string client_language = 0x1;
|
||||
optional string product = 0x2;
|
||||
optional uint32 version = 0x3;
|
||||
optional string type = 0x4;
|
||||
repeated string avoidAds = 0x5;
|
||||
}
|
||||
|
||||
message AdQueueResponse {
|
||||
repeated AdQueueEntry adQueueEntry = 0x1;
|
||||
}
|
||||
|
||||
message AdFile {
|
||||
optional string id = 0x1;
|
||||
optional string format = 0x2;
|
||||
}
|
||||
|
||||
message AdQueueEntry {
|
||||
optional uint64 start_time = 0x1;
|
||||
optional uint64 end_time = 0x2;
|
||||
optional double priority = 0x3;
|
||||
optional string token = 0x4;
|
||||
optional uint32 ad_version = 0x5;
|
||||
optional string id = 0x6;
|
||||
optional string type = 0x7;
|
||||
optional string campaign = 0x8;
|
||||
optional string advertiser = 0x9;
|
||||
optional string url = 0xa;
|
||||
optional uint64 duration = 0xb;
|
||||
optional uint64 expiry = 0xc;
|
||||
optional string tracking_url = 0xd;
|
||||
optional string banner_type = 0xe;
|
||||
optional string html = 0xf;
|
||||
optional string image = 0x10;
|
||||
optional string background_image = 0x11;
|
||||
optional string background_url = 0x12;
|
||||
optional string background_color = 0x13;
|
||||
optional string title = 0x14;
|
||||
optional string caption = 0x15;
|
||||
repeated AdFile file = 0x16;
|
||||
repeated Rule rule = 0x17;
|
||||
}
|
||||
|
||||
95
src/inputs/librespot-c/src/proto/appstore.proto
Normal file
95
src/inputs/librespot-c/src/proto/appstore.proto
Normal file
@@ -0,0 +1,95 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message AppInfo {
|
||||
optional string identifier = 0x1;
|
||||
optional int32 version_int = 0x2;
|
||||
}
|
||||
|
||||
message AppInfoList {
|
||||
repeated AppInfo items = 0x1;
|
||||
}
|
||||
|
||||
message SemanticVersion {
|
||||
optional int32 major = 0x1;
|
||||
optional int32 minor = 0x2;
|
||||
optional int32 patch = 0x3;
|
||||
}
|
||||
|
||||
message RequestHeader {
|
||||
optional string market = 0x1;
|
||||
optional Platform platform = 0x2;
|
||||
enum Platform {
|
||||
WIN32_X86 = 0x0;
|
||||
OSX_X86 = 0x1;
|
||||
LINUX_X86 = 0x2;
|
||||
IPHONE_ARM = 0x3;
|
||||
SYMBIANS60_ARM = 0x4;
|
||||
OSX_POWERPC = 0x5;
|
||||
ANDROID_ARM = 0x6;
|
||||
WINCE_ARM = 0x7;
|
||||
LINUX_X86_64 = 0x8;
|
||||
OSX_X86_64 = 0x9;
|
||||
PALM_ARM = 0xa;
|
||||
LINUX_SH = 0xb;
|
||||
FREEBSD_X86 = 0xc;
|
||||
FREEBSD_X86_64 = 0xd;
|
||||
BLACKBERRY_ARM = 0xe;
|
||||
SONOS_UNKNOWN = 0xf;
|
||||
LINUX_MIPS = 0x10;
|
||||
LINUX_ARM = 0x11;
|
||||
LOGITECH_ARM = 0x12;
|
||||
LINUX_BLACKFIN = 0x13;
|
||||
ONKYO_ARM = 0x15;
|
||||
QNXNTO_ARM = 0x16;
|
||||
BADPLATFORM = 0xff;
|
||||
}
|
||||
optional AppInfoList app_infos = 0x6;
|
||||
optional string bridge_identifier = 0x7;
|
||||
optional SemanticVersion bridge_version = 0x8;
|
||||
optional DeviceClass device_class = 0x9;
|
||||
enum DeviceClass {
|
||||
DESKTOP = 0x1;
|
||||
TABLET = 0x2;
|
||||
MOBILE = 0x3;
|
||||
WEB = 0x4;
|
||||
TV = 0x5;
|
||||
}
|
||||
}
|
||||
|
||||
message AppItem {
|
||||
optional string identifier = 0x1;
|
||||
optional Requirement requirement = 0x2;
|
||||
enum Requirement {
|
||||
REQUIRED_INSTALL = 0x1;
|
||||
LAZYLOAD = 0x2;
|
||||
OPTIONAL_INSTALL = 0x3;
|
||||
}
|
||||
optional string manifest = 0x4;
|
||||
optional string checksum = 0x5;
|
||||
optional string bundle_uri = 0x6;
|
||||
optional string small_icon_uri = 0x7;
|
||||
optional string large_icon_uri = 0x8;
|
||||
optional string medium_icon_uri = 0x9;
|
||||
optional Type bundle_type = 0xa;
|
||||
enum Type {
|
||||
APPLICATION = 0x0;
|
||||
FRAMEWORK = 0x1;
|
||||
BRIDGE = 0x2;
|
||||
}
|
||||
optional SemanticVersion version = 0xb;
|
||||
optional uint32 ttl_in_seconds = 0xc;
|
||||
optional IdentifierList categories = 0xd;
|
||||
}
|
||||
|
||||
message AppList {
|
||||
repeated AppItem items = 0x1;
|
||||
}
|
||||
|
||||
message IdentifierList {
|
||||
repeated string identifiers = 0x1;
|
||||
}
|
||||
|
||||
message BannerConfig {
|
||||
optional string json = 0x1;
|
||||
}
|
||||
|
||||
2063
src/inputs/librespot-c/src/proto/authentication.pb-c.c
Normal file
2063
src/inputs/librespot-c/src/proto/authentication.pb-c.c
Normal file
File diff suppressed because it is too large
Load Diff
697
src/inputs/librespot-c/src/proto/authentication.pb-c.h
Normal file
697
src/inputs/librespot-c/src/proto/authentication.pb-c.h
Normal file
@@ -0,0 +1,697 @@
|
||||
/* Generated by the protocol buffer compiler. DO NOT EDIT! */
|
||||
/* Generated from: proto/authentication.proto */
|
||||
|
||||
#ifndef PROTOBUF_C_proto_2fauthentication_2eproto__INCLUDED
|
||||
#define PROTOBUF_C_proto_2fauthentication_2eproto__INCLUDED
|
||||
|
||||
#include <protobuf-c/protobuf-c.h>
|
||||
|
||||
PROTOBUF_C__BEGIN_DECLS
|
||||
|
||||
#if PROTOBUF_C_VERSION_NUMBER < 1000000
|
||||
# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers.
|
||||
#elif 1003003 < PROTOBUF_C_MIN_COMPILER_VERSION
|
||||
# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c.
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct _ClientResponseEncrypted ClientResponseEncrypted;
|
||||
typedef struct _LoginCredentials LoginCredentials;
|
||||
typedef struct _FingerprintResponseUnion FingerprintResponseUnion;
|
||||
typedef struct _FingerprintGrainResponse FingerprintGrainResponse;
|
||||
typedef struct _FingerprintHmacRipemdResponse FingerprintHmacRipemdResponse;
|
||||
typedef struct _PeerTicketUnion PeerTicketUnion;
|
||||
typedef struct _PeerTicketPublicKey PeerTicketPublicKey;
|
||||
typedef struct _PeerTicketOld PeerTicketOld;
|
||||
typedef struct _SystemInfo SystemInfo;
|
||||
typedef struct _LibspotifyAppKey LibspotifyAppKey;
|
||||
typedef struct _ClientInfo ClientInfo;
|
||||
typedef struct _ClientInfoFacebook ClientInfoFacebook;
|
||||
typedef struct _APWelcome APWelcome;
|
||||
typedef struct _AccountInfo AccountInfo;
|
||||
typedef struct _AccountInfoSpotify AccountInfoSpotify;
|
||||
typedef struct _AccountInfoFacebook AccountInfoFacebook;
|
||||
|
||||
|
||||
/* --- enums --- */
|
||||
|
||||
typedef enum _AuthenticationType {
|
||||
AUTHENTICATION_TYPE__AUTHENTICATION_USER_PASS = 0,
|
||||
AUTHENTICATION_TYPE__AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS = 1,
|
||||
AUTHENTICATION_TYPE__AUTHENTICATION_STORED_FACEBOOK_CREDENTIALS = 2,
|
||||
AUTHENTICATION_TYPE__AUTHENTICATION_SPOTIFY_TOKEN = 3,
|
||||
AUTHENTICATION_TYPE__AUTHENTICATION_FACEBOOK_TOKEN = 4
|
||||
PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(AUTHENTICATION_TYPE)
|
||||
} AuthenticationType;
|
||||
typedef enum _AccountCreation {
|
||||
ACCOUNT_CREATION__ACCOUNT_CREATION_ALWAYS_PROMPT = 1,
|
||||
ACCOUNT_CREATION__ACCOUNT_CREATION_ALWAYS_CREATE = 3
|
||||
PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(ACCOUNT_CREATION)
|
||||
} AccountCreation;
|
||||
typedef enum _CpuFamily {
|
||||
CPU_FAMILY__CPU_UNKNOWN = 0,
|
||||
CPU_FAMILY__CPU_X86 = 1,
|
||||
CPU_FAMILY__CPU_X86_64 = 2,
|
||||
CPU_FAMILY__CPU_PPC = 3,
|
||||
CPU_FAMILY__CPU_PPC_64 = 4,
|
||||
CPU_FAMILY__CPU_ARM = 5,
|
||||
CPU_FAMILY__CPU_IA64 = 6,
|
||||
CPU_FAMILY__CPU_SH = 7,
|
||||
CPU_FAMILY__CPU_MIPS = 8,
|
||||
CPU_FAMILY__CPU_BLACKFIN = 9
|
||||
PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(CPU_FAMILY)
|
||||
} CpuFamily;
|
||||
typedef enum _Brand {
|
||||
BRAND__BRAND_UNBRANDED = 0,
|
||||
BRAND__BRAND_INQ = 1,
|
||||
BRAND__BRAND_HTC = 2,
|
||||
BRAND__BRAND_NOKIA = 3
|
||||
PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(BRAND)
|
||||
} Brand;
|
||||
typedef enum _Os {
|
||||
OS__OS_UNKNOWN = 0,
|
||||
OS__OS_WINDOWS = 1,
|
||||
OS__OS_OSX = 2,
|
||||
OS__OS_IPHONE = 3,
|
||||
OS__OS_S60 = 4,
|
||||
OS__OS_LINUX = 5,
|
||||
OS__OS_WINDOWS_CE = 6,
|
||||
OS__OS_ANDROID = 7,
|
||||
OS__OS_PALM = 8,
|
||||
OS__OS_FREEBSD = 9,
|
||||
OS__OS_BLACKBERRY = 10,
|
||||
OS__OS_SONOS = 11,
|
||||
OS__OS_LOGITECH = 12,
|
||||
OS__OS_WP7 = 13,
|
||||
OS__OS_ONKYO = 14,
|
||||
OS__OS_PHILIPS = 15,
|
||||
OS__OS_WD = 16,
|
||||
OS__OS_VOLVO = 17,
|
||||
OS__OS_TIVO = 18,
|
||||
OS__OS_AWOX = 19,
|
||||
OS__OS_MEEGO = 20,
|
||||
OS__OS_QNXNTO = 21,
|
||||
OS__OS_BCO = 22
|
||||
PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(OS)
|
||||
} Os;
|
||||
typedef enum _AccountType {
|
||||
ACCOUNT_TYPE__Spotify = 0,
|
||||
ACCOUNT_TYPE__Facebook = 1
|
||||
PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(ACCOUNT_TYPE)
|
||||
} AccountType;
|
||||
|
||||
/* --- messages --- */
|
||||
|
||||
struct _ClientResponseEncrypted
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
LoginCredentials *login_credentials;
|
||||
protobuf_c_boolean has_account_creation;
|
||||
AccountCreation account_creation;
|
||||
FingerprintResponseUnion *fingerprint_response;
|
||||
PeerTicketUnion *peer_ticket;
|
||||
SystemInfo *system_info;
|
||||
char *platform_model;
|
||||
char *version_string;
|
||||
LibspotifyAppKey *appkey;
|
||||
ClientInfo *client_info;
|
||||
};
|
||||
#define CLIENT_RESPONSE_ENCRYPTED__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&client_response_encrypted__descriptor) \
|
||||
, NULL, 0, ACCOUNT_CREATION__ACCOUNT_CREATION_ALWAYS_PROMPT, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
|
||||
|
||||
|
||||
struct _LoginCredentials
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
char *username;
|
||||
AuthenticationType typ;
|
||||
protobuf_c_boolean has_auth_data;
|
||||
ProtobufCBinaryData auth_data;
|
||||
};
|
||||
#define LOGIN_CREDENTIALS__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&login_credentials__descriptor) \
|
||||
, NULL, AUTHENTICATION_TYPE__AUTHENTICATION_USER_PASS, 0, {0,NULL} }
|
||||
|
||||
|
||||
struct _FingerprintResponseUnion
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
FingerprintGrainResponse *grain;
|
||||
FingerprintHmacRipemdResponse *hmac_ripemd;
|
||||
};
|
||||
#define FINGERPRINT_RESPONSE_UNION__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&fingerprint_response_union__descriptor) \
|
||||
, NULL, NULL }
|
||||
|
||||
|
||||
struct _FingerprintGrainResponse
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
ProtobufCBinaryData encrypted_key;
|
||||
};
|
||||
#define FINGERPRINT_GRAIN_RESPONSE__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&fingerprint_grain_response__descriptor) \
|
||||
, {0,NULL} }
|
||||
|
||||
|
||||
struct _FingerprintHmacRipemdResponse
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
ProtobufCBinaryData hmac;
|
||||
};
|
||||
#define FINGERPRINT_HMAC_RIPEMD_RESPONSE__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&fingerprint_hmac_ripemd_response__descriptor) \
|
||||
, {0,NULL} }
|
||||
|
||||
|
||||
struct _PeerTicketUnion
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
PeerTicketPublicKey *public_key;
|
||||
PeerTicketOld *old_ticket;
|
||||
};
|
||||
#define PEER_TICKET_UNION__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&peer_ticket_union__descriptor) \
|
||||
, NULL, NULL }
|
||||
|
||||
|
||||
struct _PeerTicketPublicKey
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
ProtobufCBinaryData public_key;
|
||||
};
|
||||
#define PEER_TICKET_PUBLIC_KEY__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&peer_ticket_public_key__descriptor) \
|
||||
, {0,NULL} }
|
||||
|
||||
|
||||
struct _PeerTicketOld
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
ProtobufCBinaryData peer_ticket;
|
||||
ProtobufCBinaryData peer_ticket_signature;
|
||||
};
|
||||
#define PEER_TICKET_OLD__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&peer_ticket_old__descriptor) \
|
||||
, {0,NULL}, {0,NULL} }
|
||||
|
||||
|
||||
struct _SystemInfo
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
CpuFamily cpu_family;
|
||||
protobuf_c_boolean has_cpu_subtype;
|
||||
uint32_t cpu_subtype;
|
||||
protobuf_c_boolean has_cpu_ext;
|
||||
uint32_t cpu_ext;
|
||||
protobuf_c_boolean has_brand;
|
||||
Brand brand;
|
||||
protobuf_c_boolean has_brand_flags;
|
||||
uint32_t brand_flags;
|
||||
Os os;
|
||||
protobuf_c_boolean has_os_version;
|
||||
uint32_t os_version;
|
||||
protobuf_c_boolean has_os_ext;
|
||||
uint32_t os_ext;
|
||||
char *system_information_string;
|
||||
char *device_id;
|
||||
};
|
||||
#define SYSTEM_INFO__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&system_info__descriptor) \
|
||||
, CPU_FAMILY__CPU_UNKNOWN, 0, 0, 0, 0, 0, BRAND__BRAND_UNBRANDED, 0, 0, OS__OS_UNKNOWN, 0, 0, 0, 0, NULL, NULL }
|
||||
|
||||
|
||||
struct _LibspotifyAppKey
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
uint32_t version;
|
||||
ProtobufCBinaryData devkey;
|
||||
ProtobufCBinaryData signature;
|
||||
char *useragent;
|
||||
ProtobufCBinaryData callback_hash;
|
||||
};
|
||||
#define LIBSPOTIFY_APP_KEY__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&libspotify_app_key__descriptor) \
|
||||
, 0, {0,NULL}, {0,NULL}, NULL, {0,NULL} }
|
||||
|
||||
|
||||
struct _ClientInfo
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
protobuf_c_boolean has_limited;
|
||||
protobuf_c_boolean limited;
|
||||
ClientInfoFacebook *fb;
|
||||
char *language;
|
||||
};
|
||||
#define CLIENT_INFO__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&client_info__descriptor) \
|
||||
, 0, 0, NULL, NULL }
|
||||
|
||||
|
||||
struct _ClientInfoFacebook
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
char *machine_id;
|
||||
};
|
||||
#define CLIENT_INFO_FACEBOOK__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&client_info_facebook__descriptor) \
|
||||
, NULL }
|
||||
|
||||
|
||||
struct _APWelcome
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
char *canonical_username;
|
||||
AccountType account_type_logged_in;
|
||||
AccountType credentials_type_logged_in;
|
||||
AuthenticationType reusable_auth_credentials_type;
|
||||
ProtobufCBinaryData reusable_auth_credentials;
|
||||
protobuf_c_boolean has_lfs_secret;
|
||||
ProtobufCBinaryData lfs_secret;
|
||||
AccountInfo *account_info;
|
||||
AccountInfoFacebook *fb;
|
||||
};
|
||||
#define APWELCOME__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&apwelcome__descriptor) \
|
||||
, NULL, ACCOUNT_TYPE__Spotify, ACCOUNT_TYPE__Spotify, AUTHENTICATION_TYPE__AUTHENTICATION_USER_PASS, {0,NULL}, 0, {0,NULL}, NULL, NULL }
|
||||
|
||||
|
||||
struct _AccountInfo
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
AccountInfoSpotify *spotify;
|
||||
AccountInfoFacebook *facebook;
|
||||
};
|
||||
#define ACCOUNT_INFO__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&account_info__descriptor) \
|
||||
, NULL, NULL }
|
||||
|
||||
|
||||
struct _AccountInfoSpotify
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
};
|
||||
#define ACCOUNT_INFO_SPOTIFY__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&account_info_spotify__descriptor) \
|
||||
}
|
||||
|
||||
|
||||
struct _AccountInfoFacebook
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
char *access_token;
|
||||
char *machine_id;
|
||||
};
|
||||
#define ACCOUNT_INFO_FACEBOOK__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&account_info_facebook__descriptor) \
|
||||
, NULL, NULL }
|
||||
|
||||
|
||||
/* ClientResponseEncrypted methods */
|
||||
void client_response_encrypted__init
|
||||
(ClientResponseEncrypted *message);
|
||||
size_t client_response_encrypted__get_packed_size
|
||||
(const ClientResponseEncrypted *message);
|
||||
size_t client_response_encrypted__pack
|
||||
(const ClientResponseEncrypted *message,
|
||||
uint8_t *out);
|
||||
size_t client_response_encrypted__pack_to_buffer
|
||||
(const ClientResponseEncrypted *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
ClientResponseEncrypted *
|
||||
client_response_encrypted__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void client_response_encrypted__free_unpacked
|
||||
(ClientResponseEncrypted *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* LoginCredentials methods */
|
||||
void login_credentials__init
|
||||
(LoginCredentials *message);
|
||||
size_t login_credentials__get_packed_size
|
||||
(const LoginCredentials *message);
|
||||
size_t login_credentials__pack
|
||||
(const LoginCredentials *message,
|
||||
uint8_t *out);
|
||||
size_t login_credentials__pack_to_buffer
|
||||
(const LoginCredentials *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
LoginCredentials *
|
||||
login_credentials__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void login_credentials__free_unpacked
|
||||
(LoginCredentials *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* FingerprintResponseUnion methods */
|
||||
void fingerprint_response_union__init
|
||||
(FingerprintResponseUnion *message);
|
||||
size_t fingerprint_response_union__get_packed_size
|
||||
(const FingerprintResponseUnion *message);
|
||||
size_t fingerprint_response_union__pack
|
||||
(const FingerprintResponseUnion *message,
|
||||
uint8_t *out);
|
||||
size_t fingerprint_response_union__pack_to_buffer
|
||||
(const FingerprintResponseUnion *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
FingerprintResponseUnion *
|
||||
fingerprint_response_union__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void fingerprint_response_union__free_unpacked
|
||||
(FingerprintResponseUnion *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* FingerprintGrainResponse methods */
|
||||
void fingerprint_grain_response__init
|
||||
(FingerprintGrainResponse *message);
|
||||
size_t fingerprint_grain_response__get_packed_size
|
||||
(const FingerprintGrainResponse *message);
|
||||
size_t fingerprint_grain_response__pack
|
||||
(const FingerprintGrainResponse *message,
|
||||
uint8_t *out);
|
||||
size_t fingerprint_grain_response__pack_to_buffer
|
||||
(const FingerprintGrainResponse *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
FingerprintGrainResponse *
|
||||
fingerprint_grain_response__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void fingerprint_grain_response__free_unpacked
|
||||
(FingerprintGrainResponse *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* FingerprintHmacRipemdResponse methods */
|
||||
void fingerprint_hmac_ripemd_response__init
|
||||
(FingerprintHmacRipemdResponse *message);
|
||||
size_t fingerprint_hmac_ripemd_response__get_packed_size
|
||||
(const FingerprintHmacRipemdResponse *message);
|
||||
size_t fingerprint_hmac_ripemd_response__pack
|
||||
(const FingerprintHmacRipemdResponse *message,
|
||||
uint8_t *out);
|
||||
size_t fingerprint_hmac_ripemd_response__pack_to_buffer
|
||||
(const FingerprintHmacRipemdResponse *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
FingerprintHmacRipemdResponse *
|
||||
fingerprint_hmac_ripemd_response__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void fingerprint_hmac_ripemd_response__free_unpacked
|
||||
(FingerprintHmacRipemdResponse *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* PeerTicketUnion methods */
|
||||
void peer_ticket_union__init
|
||||
(PeerTicketUnion *message);
|
||||
size_t peer_ticket_union__get_packed_size
|
||||
(const PeerTicketUnion *message);
|
||||
size_t peer_ticket_union__pack
|
||||
(const PeerTicketUnion *message,
|
||||
uint8_t *out);
|
||||
size_t peer_ticket_union__pack_to_buffer
|
||||
(const PeerTicketUnion *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
PeerTicketUnion *
|
||||
peer_ticket_union__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void peer_ticket_union__free_unpacked
|
||||
(PeerTicketUnion *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* PeerTicketPublicKey methods */
|
||||
void peer_ticket_public_key__init
|
||||
(PeerTicketPublicKey *message);
|
||||
size_t peer_ticket_public_key__get_packed_size
|
||||
(const PeerTicketPublicKey *message);
|
||||
size_t peer_ticket_public_key__pack
|
||||
(const PeerTicketPublicKey *message,
|
||||
uint8_t *out);
|
||||
size_t peer_ticket_public_key__pack_to_buffer
|
||||
(const PeerTicketPublicKey *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
PeerTicketPublicKey *
|
||||
peer_ticket_public_key__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void peer_ticket_public_key__free_unpacked
|
||||
(PeerTicketPublicKey *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* PeerTicketOld methods */
|
||||
void peer_ticket_old__init
|
||||
(PeerTicketOld *message);
|
||||
size_t peer_ticket_old__get_packed_size
|
||||
(const PeerTicketOld *message);
|
||||
size_t peer_ticket_old__pack
|
||||
(const PeerTicketOld *message,
|
||||
uint8_t *out);
|
||||
size_t peer_ticket_old__pack_to_buffer
|
||||
(const PeerTicketOld *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
PeerTicketOld *
|
||||
peer_ticket_old__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void peer_ticket_old__free_unpacked
|
||||
(PeerTicketOld *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* SystemInfo methods */
|
||||
void system_info__init
|
||||
(SystemInfo *message);
|
||||
size_t system_info__get_packed_size
|
||||
(const SystemInfo *message);
|
||||
size_t system_info__pack
|
||||
(const SystemInfo *message,
|
||||
uint8_t *out);
|
||||
size_t system_info__pack_to_buffer
|
||||
(const SystemInfo *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
SystemInfo *
|
||||
system_info__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void system_info__free_unpacked
|
||||
(SystemInfo *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* LibspotifyAppKey methods */
|
||||
void libspotify_app_key__init
|
||||
(LibspotifyAppKey *message);
|
||||
size_t libspotify_app_key__get_packed_size
|
||||
(const LibspotifyAppKey *message);
|
||||
size_t libspotify_app_key__pack
|
||||
(const LibspotifyAppKey *message,
|
||||
uint8_t *out);
|
||||
size_t libspotify_app_key__pack_to_buffer
|
||||
(const LibspotifyAppKey *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
LibspotifyAppKey *
|
||||
libspotify_app_key__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void libspotify_app_key__free_unpacked
|
||||
(LibspotifyAppKey *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* ClientInfo methods */
|
||||
void client_info__init
|
||||
(ClientInfo *message);
|
||||
size_t client_info__get_packed_size
|
||||
(const ClientInfo *message);
|
||||
size_t client_info__pack
|
||||
(const ClientInfo *message,
|
||||
uint8_t *out);
|
||||
size_t client_info__pack_to_buffer
|
||||
(const ClientInfo *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
ClientInfo *
|
||||
client_info__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void client_info__free_unpacked
|
||||
(ClientInfo *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* ClientInfoFacebook methods */
|
||||
void client_info_facebook__init
|
||||
(ClientInfoFacebook *message);
|
||||
size_t client_info_facebook__get_packed_size
|
||||
(const ClientInfoFacebook *message);
|
||||
size_t client_info_facebook__pack
|
||||
(const ClientInfoFacebook *message,
|
||||
uint8_t *out);
|
||||
size_t client_info_facebook__pack_to_buffer
|
||||
(const ClientInfoFacebook *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
ClientInfoFacebook *
|
||||
client_info_facebook__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void client_info_facebook__free_unpacked
|
||||
(ClientInfoFacebook *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* APWelcome methods */
|
||||
void apwelcome__init
|
||||
(APWelcome *message);
|
||||
size_t apwelcome__get_packed_size
|
||||
(const APWelcome *message);
|
||||
size_t apwelcome__pack
|
||||
(const APWelcome *message,
|
||||
uint8_t *out);
|
||||
size_t apwelcome__pack_to_buffer
|
||||
(const APWelcome *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
APWelcome *
|
||||
apwelcome__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void apwelcome__free_unpacked
|
||||
(APWelcome *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* AccountInfo methods */
|
||||
void account_info__init
|
||||
(AccountInfo *message);
|
||||
size_t account_info__get_packed_size
|
||||
(const AccountInfo *message);
|
||||
size_t account_info__pack
|
||||
(const AccountInfo *message,
|
||||
uint8_t *out);
|
||||
size_t account_info__pack_to_buffer
|
||||
(const AccountInfo *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
AccountInfo *
|
||||
account_info__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void account_info__free_unpacked
|
||||
(AccountInfo *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* AccountInfoSpotify methods */
|
||||
void account_info_spotify__init
|
||||
(AccountInfoSpotify *message);
|
||||
size_t account_info_spotify__get_packed_size
|
||||
(const AccountInfoSpotify *message);
|
||||
size_t account_info_spotify__pack
|
||||
(const AccountInfoSpotify *message,
|
||||
uint8_t *out);
|
||||
size_t account_info_spotify__pack_to_buffer
|
||||
(const AccountInfoSpotify *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
AccountInfoSpotify *
|
||||
account_info_spotify__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void account_info_spotify__free_unpacked
|
||||
(AccountInfoSpotify *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* AccountInfoFacebook methods */
|
||||
void account_info_facebook__init
|
||||
(AccountInfoFacebook *message);
|
||||
size_t account_info_facebook__get_packed_size
|
||||
(const AccountInfoFacebook *message);
|
||||
size_t account_info_facebook__pack
|
||||
(const AccountInfoFacebook *message,
|
||||
uint8_t *out);
|
||||
size_t account_info_facebook__pack_to_buffer
|
||||
(const AccountInfoFacebook *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
AccountInfoFacebook *
|
||||
account_info_facebook__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void account_info_facebook__free_unpacked
|
||||
(AccountInfoFacebook *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* --- per-message closures --- */
|
||||
|
||||
typedef void (*ClientResponseEncrypted_Closure)
|
||||
(const ClientResponseEncrypted *message,
|
||||
void *closure_data);
|
||||
typedef void (*LoginCredentials_Closure)
|
||||
(const LoginCredentials *message,
|
||||
void *closure_data);
|
||||
typedef void (*FingerprintResponseUnion_Closure)
|
||||
(const FingerprintResponseUnion *message,
|
||||
void *closure_data);
|
||||
typedef void (*FingerprintGrainResponse_Closure)
|
||||
(const FingerprintGrainResponse *message,
|
||||
void *closure_data);
|
||||
typedef void (*FingerprintHmacRipemdResponse_Closure)
|
||||
(const FingerprintHmacRipemdResponse *message,
|
||||
void *closure_data);
|
||||
typedef void (*PeerTicketUnion_Closure)
|
||||
(const PeerTicketUnion *message,
|
||||
void *closure_data);
|
||||
typedef void (*PeerTicketPublicKey_Closure)
|
||||
(const PeerTicketPublicKey *message,
|
||||
void *closure_data);
|
||||
typedef void (*PeerTicketOld_Closure)
|
||||
(const PeerTicketOld *message,
|
||||
void *closure_data);
|
||||
typedef void (*SystemInfo_Closure)
|
||||
(const SystemInfo *message,
|
||||
void *closure_data);
|
||||
typedef void (*LibspotifyAppKey_Closure)
|
||||
(const LibspotifyAppKey *message,
|
||||
void *closure_data);
|
||||
typedef void (*ClientInfo_Closure)
|
||||
(const ClientInfo *message,
|
||||
void *closure_data);
|
||||
typedef void (*ClientInfoFacebook_Closure)
|
||||
(const ClientInfoFacebook *message,
|
||||
void *closure_data);
|
||||
typedef void (*APWelcome_Closure)
|
||||
(const APWelcome *message,
|
||||
void *closure_data);
|
||||
typedef void (*AccountInfo_Closure)
|
||||
(const AccountInfo *message,
|
||||
void *closure_data);
|
||||
typedef void (*AccountInfoSpotify_Closure)
|
||||
(const AccountInfoSpotify *message,
|
||||
void *closure_data);
|
||||
typedef void (*AccountInfoFacebook_Closure)
|
||||
(const AccountInfoFacebook *message,
|
||||
void *closure_data);
|
||||
|
||||
/* --- services --- */
|
||||
|
||||
|
||||
/* --- descriptors --- */
|
||||
|
||||
extern const ProtobufCEnumDescriptor authentication_type__descriptor;
|
||||
extern const ProtobufCEnumDescriptor account_creation__descriptor;
|
||||
extern const ProtobufCEnumDescriptor cpu_family__descriptor;
|
||||
extern const ProtobufCEnumDescriptor brand__descriptor;
|
||||
extern const ProtobufCEnumDescriptor os__descriptor;
|
||||
extern const ProtobufCEnumDescriptor account_type__descriptor;
|
||||
extern const ProtobufCMessageDescriptor client_response_encrypted__descriptor;
|
||||
extern const ProtobufCMessageDescriptor login_credentials__descriptor;
|
||||
extern const ProtobufCMessageDescriptor fingerprint_response_union__descriptor;
|
||||
extern const ProtobufCMessageDescriptor fingerprint_grain_response__descriptor;
|
||||
extern const ProtobufCMessageDescriptor fingerprint_hmac_ripemd_response__descriptor;
|
||||
extern const ProtobufCMessageDescriptor peer_ticket_union__descriptor;
|
||||
extern const ProtobufCMessageDescriptor peer_ticket_public_key__descriptor;
|
||||
extern const ProtobufCMessageDescriptor peer_ticket_old__descriptor;
|
||||
extern const ProtobufCMessageDescriptor system_info__descriptor;
|
||||
extern const ProtobufCMessageDescriptor libspotify_app_key__descriptor;
|
||||
extern const ProtobufCMessageDescriptor client_info__descriptor;
|
||||
extern const ProtobufCMessageDescriptor client_info_facebook__descriptor;
|
||||
extern const ProtobufCMessageDescriptor apwelcome__descriptor;
|
||||
extern const ProtobufCMessageDescriptor account_info__descriptor;
|
||||
extern const ProtobufCMessageDescriptor account_info_spotify__descriptor;
|
||||
extern const ProtobufCMessageDescriptor account_info_facebook__descriptor;
|
||||
|
||||
PROTOBUF_C__END_DECLS
|
||||
|
||||
|
||||
#endif /* PROTOBUF_C_proto_2fauthentication_2eproto__INCLUDED */
|
||||
165
src/inputs/librespot-c/src/proto/authentication.proto
Normal file
165
src/inputs/librespot-c/src/proto/authentication.proto
Normal file
@@ -0,0 +1,165 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message ClientResponseEncrypted {
|
||||
required LoginCredentials login_credentials = 0xa;
|
||||
optional AccountCreation account_creation = 0x14;
|
||||
optional FingerprintResponseUnion fingerprint_response = 0x1e;
|
||||
optional PeerTicketUnion peer_ticket = 0x28;
|
||||
required SystemInfo system_info = 0x32;
|
||||
optional string platform_model = 0x3c;
|
||||
optional string version_string = 0x46;
|
||||
optional LibspotifyAppKey appkey = 0x50;
|
||||
optional ClientInfo client_info = 0x5a;
|
||||
}
|
||||
|
||||
message LoginCredentials {
|
||||
optional string username = 0xa;
|
||||
required AuthenticationType typ = 0x14;
|
||||
optional bytes auth_data = 0x1e;
|
||||
}
|
||||
|
||||
enum AuthenticationType {
|
||||
AUTHENTICATION_USER_PASS = 0x0;
|
||||
AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS = 0x1;
|
||||
AUTHENTICATION_STORED_FACEBOOK_CREDENTIALS = 0x2;
|
||||
AUTHENTICATION_SPOTIFY_TOKEN = 0x3;
|
||||
AUTHENTICATION_FACEBOOK_TOKEN = 0x4;
|
||||
}
|
||||
|
||||
enum AccountCreation {
|
||||
ACCOUNT_CREATION_ALWAYS_PROMPT = 0x1;
|
||||
ACCOUNT_CREATION_ALWAYS_CREATE = 0x3;
|
||||
}
|
||||
|
||||
message FingerprintResponseUnion {
|
||||
optional FingerprintGrainResponse grain = 0xa;
|
||||
optional FingerprintHmacRipemdResponse hmac_ripemd = 0x14;
|
||||
}
|
||||
|
||||
message FingerprintGrainResponse {
|
||||
required bytes encrypted_key = 0xa;
|
||||
}
|
||||
|
||||
message FingerprintHmacRipemdResponse {
|
||||
required bytes hmac = 0xa;
|
||||
}
|
||||
|
||||
message PeerTicketUnion {
|
||||
optional PeerTicketPublicKey public_key = 0xa;
|
||||
optional PeerTicketOld old_ticket = 0x14;
|
||||
}
|
||||
|
||||
message PeerTicketPublicKey {
|
||||
required bytes public_key = 0xa;
|
||||
}
|
||||
|
||||
message PeerTicketOld {
|
||||
required bytes peer_ticket = 0xa;
|
||||
required bytes peer_ticket_signature = 0x14;
|
||||
}
|
||||
|
||||
message SystemInfo {
|
||||
required CpuFamily cpu_family = 0xa;
|
||||
optional uint32 cpu_subtype = 0x14;
|
||||
optional uint32 cpu_ext = 0x1e;
|
||||
optional Brand brand = 0x28;
|
||||
optional uint32 brand_flags = 0x32;
|
||||
required Os os = 0x3c;
|
||||
optional uint32 os_version = 0x46;
|
||||
optional uint32 os_ext = 0x50;
|
||||
optional string system_information_string = 0x5a;
|
||||
optional string device_id = 0x64;
|
||||
}
|
||||
|
||||
enum CpuFamily {
|
||||
CPU_UNKNOWN = 0x0;
|
||||
CPU_X86 = 0x1;
|
||||
CPU_X86_64 = 0x2;
|
||||
CPU_PPC = 0x3;
|
||||
CPU_PPC_64 = 0x4;
|
||||
CPU_ARM = 0x5;
|
||||
CPU_IA64 = 0x6;
|
||||
CPU_SH = 0x7;
|
||||
CPU_MIPS = 0x8;
|
||||
CPU_BLACKFIN = 0x9;
|
||||
}
|
||||
|
||||
enum Brand {
|
||||
BRAND_UNBRANDED = 0x0;
|
||||
BRAND_INQ = 0x1;
|
||||
BRAND_HTC = 0x2;
|
||||
BRAND_NOKIA = 0x3;
|
||||
}
|
||||
|
||||
enum Os {
|
||||
OS_UNKNOWN = 0x0;
|
||||
OS_WINDOWS = 0x1;
|
||||
OS_OSX = 0x2;
|
||||
OS_IPHONE = 0x3;
|
||||
OS_S60 = 0x4;
|
||||
OS_LINUX = 0x5;
|
||||
OS_WINDOWS_CE = 0x6;
|
||||
OS_ANDROID = 0x7;
|
||||
OS_PALM = 0x8;
|
||||
OS_FREEBSD = 0x9;
|
||||
OS_BLACKBERRY = 0xa;
|
||||
OS_SONOS = 0xb;
|
||||
OS_LOGITECH = 0xc;
|
||||
OS_WP7 = 0xd;
|
||||
OS_ONKYO = 0xe;
|
||||
OS_PHILIPS = 0xf;
|
||||
OS_WD = 0x10;
|
||||
OS_VOLVO = 0x11;
|
||||
OS_TIVO = 0x12;
|
||||
OS_AWOX = 0x13;
|
||||
OS_MEEGO = 0x14;
|
||||
OS_QNXNTO = 0x15;
|
||||
OS_BCO = 0x16;
|
||||
}
|
||||
|
||||
message LibspotifyAppKey {
|
||||
required uint32 version = 0x1;
|
||||
required bytes devkey = 0x2;
|
||||
required bytes signature = 0x3;
|
||||
required string useragent = 0x4;
|
||||
required bytes callback_hash = 0x5;
|
||||
}
|
||||
|
||||
message ClientInfo {
|
||||
optional bool limited = 0x1;
|
||||
optional ClientInfoFacebook fb = 0x2;
|
||||
optional string language = 0x3;
|
||||
}
|
||||
|
||||
message ClientInfoFacebook {
|
||||
optional string machine_id = 0x1;
|
||||
}
|
||||
|
||||
message APWelcome {
|
||||
required string canonical_username = 0xa;
|
||||
required AccountType account_type_logged_in = 0x14;
|
||||
required AccountType credentials_type_logged_in = 0x19;
|
||||
required AuthenticationType reusable_auth_credentials_type = 0x1e;
|
||||
required bytes reusable_auth_credentials = 0x28;
|
||||
optional bytes lfs_secret = 0x32;
|
||||
optional AccountInfo account_info = 0x3c;
|
||||
optional AccountInfoFacebook fb = 0x46;
|
||||
}
|
||||
|
||||
enum AccountType {
|
||||
Spotify = 0x0;
|
||||
Facebook = 0x1;
|
||||
}
|
||||
|
||||
message AccountInfo {
|
||||
optional AccountInfoSpotify spotify = 0x1;
|
||||
optional AccountInfoFacebook facebook = 0x2;
|
||||
}
|
||||
|
||||
message AccountInfoSpotify {
|
||||
}
|
||||
|
||||
message AccountInfoFacebook {
|
||||
optional string access_token = 0x1;
|
||||
optional string machine_id = 0x2;
|
||||
}
|
||||
51
src/inputs/librespot-c/src/proto/facebook-publish.proto
Normal file
51
src/inputs/librespot-c/src/proto/facebook-publish.proto
Normal file
@@ -0,0 +1,51 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message EventReply {
|
||||
optional int32 queued = 0x1;
|
||||
optional RetryInfo retry = 0x2;
|
||||
}
|
||||
|
||||
message RetryInfo {
|
||||
optional int32 retry_delay = 0x1;
|
||||
optional int32 max_retry = 0x2;
|
||||
}
|
||||
|
||||
message Id {
|
||||
optional string uri = 0x1;
|
||||
optional int64 start_time = 0x2;
|
||||
}
|
||||
|
||||
message Start {
|
||||
optional int32 length = 0x1;
|
||||
optional string context_uri = 0x2;
|
||||
optional int64 end_time = 0x3;
|
||||
}
|
||||
|
||||
message Seek {
|
||||
optional int64 end_time = 0x1;
|
||||
}
|
||||
|
||||
message Pause {
|
||||
optional int32 seconds_played = 0x1;
|
||||
optional int64 end_time = 0x2;
|
||||
}
|
||||
|
||||
message Resume {
|
||||
optional int32 seconds_played = 0x1;
|
||||
optional int64 end_time = 0x2;
|
||||
}
|
||||
|
||||
message End {
|
||||
optional int32 seconds_played = 0x1;
|
||||
optional int64 end_time = 0x2;
|
||||
}
|
||||
|
||||
message Event {
|
||||
optional Id id = 0x1;
|
||||
optional Start start = 0x2;
|
||||
optional Seek seek = 0x3;
|
||||
optional Pause pause = 0x4;
|
||||
optional Resume resume = 0x5;
|
||||
optional End end = 0x6;
|
||||
}
|
||||
|
||||
183
src/inputs/librespot-c/src/proto/facebook.proto
Normal file
183
src/inputs/librespot-c/src/proto/facebook.proto
Normal file
@@ -0,0 +1,183 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message Credential {
|
||||
optional string facebook_uid = 0x1;
|
||||
optional string access_token = 0x2;
|
||||
}
|
||||
|
||||
message EnableRequest {
|
||||
optional Credential credential = 0x1;
|
||||
}
|
||||
|
||||
message EnableReply {
|
||||
optional Credential credential = 0x1;
|
||||
}
|
||||
|
||||
message DisableRequest {
|
||||
optional Credential credential = 0x1;
|
||||
}
|
||||
|
||||
message RevokeRequest {
|
||||
optional Credential credential = 0x1;
|
||||
}
|
||||
|
||||
message InspectCredentialRequest {
|
||||
optional Credential credential = 0x1;
|
||||
}
|
||||
|
||||
message InspectCredentialReply {
|
||||
optional Credential alternative_credential = 0x1;
|
||||
optional bool app_user = 0x2;
|
||||
optional bool permanent_error = 0x3;
|
||||
optional bool transient_error = 0x4;
|
||||
}
|
||||
|
||||
message UserState {
|
||||
optional Credential credential = 0x1;
|
||||
}
|
||||
|
||||
message UpdateUserStateRequest {
|
||||
optional Credential credential = 0x1;
|
||||
}
|
||||
|
||||
message OpenGraphError {
|
||||
repeated string permanent = 0x1;
|
||||
repeated string invalid_token = 0x2;
|
||||
repeated string retries = 0x3;
|
||||
}
|
||||
|
||||
message OpenGraphScrobble {
|
||||
optional int32 create_delay = 0x1;
|
||||
}
|
||||
|
||||
message OpenGraphConfig {
|
||||
optional OpenGraphError error = 0x1;
|
||||
optional OpenGraphScrobble scrobble = 0x2;
|
||||
}
|
||||
|
||||
message AuthConfig {
|
||||
optional string url = 0x1;
|
||||
repeated string permissions = 0x2;
|
||||
repeated string blacklist = 0x3;
|
||||
repeated string whitelist = 0x4;
|
||||
repeated string cancel = 0x5;
|
||||
}
|
||||
|
||||
message ConfigReply {
|
||||
optional string domain = 0x1;
|
||||
optional string app_id = 0x2;
|
||||
optional string app_namespace = 0x3;
|
||||
optional AuthConfig auth = 0x4;
|
||||
optional OpenGraphConfig og = 0x5;
|
||||
}
|
||||
|
||||
message UserFields {
|
||||
optional bool app_user = 0x1;
|
||||
optional bool display_name = 0x2;
|
||||
optional bool first_name = 0x3;
|
||||
optional bool middle_name = 0x4;
|
||||
optional bool last_name = 0x5;
|
||||
optional bool picture_large = 0x6;
|
||||
optional bool picture_square = 0x7;
|
||||
optional bool gender = 0x8;
|
||||
optional bool email = 0x9;
|
||||
}
|
||||
|
||||
message UserOptions {
|
||||
optional bool cache_is_king = 0x1;
|
||||
}
|
||||
|
||||
message UserRequest {
|
||||
optional UserOptions options = 0x1;
|
||||
optional UserFields fields = 0x2;
|
||||
}
|
||||
|
||||
message User {
|
||||
optional string spotify_username = 0x1;
|
||||
optional string facebook_uid = 0x2;
|
||||
optional bool app_user = 0x3;
|
||||
optional string display_name = 0x4;
|
||||
optional string first_name = 0x5;
|
||||
optional string middle_name = 0x6;
|
||||
optional string last_name = 0x7;
|
||||
optional string picture_large = 0x8;
|
||||
optional string picture_square = 0x9;
|
||||
optional string gender = 0xa;
|
||||
optional string email = 0xb;
|
||||
}
|
||||
|
||||
message FriendsFields {
|
||||
optional bool app_user = 0x1;
|
||||
optional bool display_name = 0x2;
|
||||
optional bool picture_large = 0x6;
|
||||
}
|
||||
|
||||
message FriendsOptions {
|
||||
optional int32 limit = 0x1;
|
||||
optional int32 offset = 0x2;
|
||||
optional bool cache_is_king = 0x3;
|
||||
optional bool app_friends = 0x4;
|
||||
optional bool non_app_friends = 0x5;
|
||||
}
|
||||
|
||||
message FriendsRequest {
|
||||
optional FriendsOptions options = 0x1;
|
||||
optional FriendsFields fields = 0x2;
|
||||
}
|
||||
|
||||
message FriendsReply {
|
||||
repeated User friends = 0x1;
|
||||
optional bool more = 0x2;
|
||||
}
|
||||
|
||||
message ShareRequest {
|
||||
optional Credential credential = 0x1;
|
||||
optional string uri = 0x2;
|
||||
optional string message_text = 0x3;
|
||||
}
|
||||
|
||||
message ShareReply {
|
||||
optional string post_id = 0x1;
|
||||
}
|
||||
|
||||
message InboxRequest {
|
||||
optional Credential credential = 0x1;
|
||||
repeated string facebook_uids = 0x3;
|
||||
optional string message_text = 0x4;
|
||||
optional string message_link = 0x5;
|
||||
}
|
||||
|
||||
message InboxReply {
|
||||
optional string message_id = 0x1;
|
||||
optional string thread_id = 0x2;
|
||||
}
|
||||
|
||||
message PermissionsOptions {
|
||||
optional bool cache_is_king = 0x1;
|
||||
}
|
||||
|
||||
message PermissionsRequest {
|
||||
optional Credential credential = 0x1;
|
||||
optional PermissionsOptions options = 0x2;
|
||||
}
|
||||
|
||||
message PermissionsReply {
|
||||
repeated string permissions = 0x1;
|
||||
}
|
||||
|
||||
message GrantPermissionsRequest {
|
||||
optional Credential credential = 0x1;
|
||||
repeated string permissions = 0x2;
|
||||
}
|
||||
|
||||
message GrantPermissionsReply {
|
||||
repeated string granted = 0x1;
|
||||
repeated string failed = 0x2;
|
||||
}
|
||||
|
||||
message TransferRequest {
|
||||
optional Credential credential = 0x1;
|
||||
optional string source_username = 0x2;
|
||||
optional string target_username = 0x3;
|
||||
}
|
||||
|
||||
2933
src/inputs/librespot-c/src/proto/keyexchange.pb-c.c
Normal file
2933
src/inputs/librespot-c/src/proto/keyexchange.pb-c.c
Normal file
File diff suppressed because it is too large
Load Diff
1076
src/inputs/librespot-c/src/proto/keyexchange.pb-c.h
Normal file
1076
src/inputs/librespot-c/src/proto/keyexchange.pb-c.h
Normal file
File diff suppressed because it is too large
Load Diff
228
src/inputs/librespot-c/src/proto/keyexchange.proto
Normal file
228
src/inputs/librespot-c/src/proto/keyexchange.proto
Normal file
@@ -0,0 +1,228 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message ClientHello {
|
||||
required BuildInfo build_info = 0xa;
|
||||
repeated Fingerprint fingerprints_supported = 0x14;
|
||||
repeated Cryptosuite cryptosuites_supported = 0x1e;
|
||||
repeated Powscheme powschemes_supported = 0x28;
|
||||
required LoginCryptoHelloUnion login_crypto_hello = 0x32;
|
||||
required bytes client_nonce = 0x3c;
|
||||
optional bytes padding = 0x46;
|
||||
optional FeatureSet feature_set = 0x50;
|
||||
}
|
||||
|
||||
|
||||
message BuildInfo {
|
||||
required Product product = 0xa;
|
||||
repeated ProductFlags product_flags = 0x14;
|
||||
required Platform platform = 0x1e;
|
||||
required uint64 version = 0x28;
|
||||
}
|
||||
|
||||
enum Product {
|
||||
PRODUCT_CLIENT = 0x0;
|
||||
PRODUCT_LIBSPOTIFY= 0x1;
|
||||
PRODUCT_MOBILE = 0x2;
|
||||
PRODUCT_PARTNER = 0x3;
|
||||
PRODUCT_LIBSPOTIFY_EMBEDDED = 0x5;
|
||||
}
|
||||
|
||||
enum ProductFlags {
|
||||
PRODUCT_FLAG_NONE = 0x0;
|
||||
PRODUCT_FLAG_DEV_BUILD = 0x1;
|
||||
}
|
||||
|
||||
enum Platform {
|
||||
PLATFORM_WIN32_X86 = 0x0;
|
||||
PLATFORM_OSX_X86 = 0x1;
|
||||
PLATFORM_LINUX_X86 = 0x2;
|
||||
PLATFORM_IPHONE_ARM = 0x3;
|
||||
PLATFORM_S60_ARM = 0x4;
|
||||
PLATFORM_OSX_PPC = 0x5;
|
||||
PLATFORM_ANDROID_ARM = 0x6;
|
||||
PLATFORM_WINDOWS_CE_ARM = 0x7;
|
||||
PLATFORM_LINUX_X86_64 = 0x8;
|
||||
PLATFORM_OSX_X86_64 = 0x9;
|
||||
PLATFORM_PALM_ARM = 0xa;
|
||||
PLATFORM_LINUX_SH = 0xb;
|
||||
PLATFORM_FREEBSD_X86 = 0xc;
|
||||
PLATFORM_FREEBSD_X86_64 = 0xd;
|
||||
PLATFORM_BLACKBERRY_ARM = 0xe;
|
||||
PLATFORM_SONOS = 0xf;
|
||||
PLATFORM_LINUX_MIPS = 0x10;
|
||||
PLATFORM_LINUX_ARM = 0x11;
|
||||
PLATFORM_LOGITECH_ARM = 0x12;
|
||||
PLATFORM_LINUX_BLACKFIN = 0x13;
|
||||
PLATFORM_WP7_ARM = 0x14;
|
||||
PLATFORM_ONKYO_ARM = 0x15;
|
||||
PLATFORM_QNXNTO_ARM = 0x16;
|
||||
PLATFORM_BCO_ARM = 0x17;
|
||||
}
|
||||
|
||||
enum Fingerprint {
|
||||
FINGERPRINT_GRAIN = 0x0;
|
||||
FINGERPRINT_HMAC_RIPEMD = 0x1;
|
||||
}
|
||||
|
||||
enum Cryptosuite {
|
||||
CRYPTO_SUITE_SHANNON = 0x0;
|
||||
CRYPTO_SUITE_RC4_SHA1_HMAC = 0x1;
|
||||
}
|
||||
|
||||
enum Powscheme {
|
||||
POW_HASH_CASH = 0x0;
|
||||
}
|
||||
|
||||
|
||||
message LoginCryptoHelloUnion {
|
||||
optional LoginCryptoDiffieHellmanHello diffie_hellman = 0xa;
|
||||
}
|
||||
|
||||
|
||||
message LoginCryptoDiffieHellmanHello {
|
||||
required bytes gc = 0xa;
|
||||
required uint32 server_keys_known = 0x14;
|
||||
}
|
||||
|
||||
|
||||
message FeatureSet {
|
||||
optional bool autoupdate2 = 0x1;
|
||||
optional bool current_location = 0x2;
|
||||
}
|
||||
|
||||
|
||||
message APResponseMessage {
|
||||
optional APChallenge challenge = 0xa;
|
||||
optional UpgradeRequiredMessage upgrade = 0x14;
|
||||
optional APLoginFailed login_failed = 0x1e;
|
||||
}
|
||||
|
||||
message APChallenge {
|
||||
required LoginCryptoChallengeUnion login_crypto_challenge = 0xa;
|
||||
required FingerprintChallengeUnion fingerprint_challenge = 0x14;
|
||||
required PoWChallengeUnion pow_challenge = 0x1e;
|
||||
required CryptoChallengeUnion crypto_challenge = 0x28;
|
||||
required bytes server_nonce = 0x32;
|
||||
optional bytes padding = 0x3c;
|
||||
}
|
||||
|
||||
message LoginCryptoChallengeUnion {
|
||||
optional LoginCryptoDiffieHellmanChallenge diffie_hellman = 0xa;
|
||||
}
|
||||
|
||||
message LoginCryptoDiffieHellmanChallenge {
|
||||
required bytes gs = 0xa;
|
||||
required int32 server_signature_key = 0x14;
|
||||
required bytes gs_signature = 0x1e;
|
||||
}
|
||||
|
||||
message FingerprintChallengeUnion {
|
||||
optional FingerprintGrainChallenge grain = 0xa;
|
||||
optional FingerprintHmacRipemdChallenge hmac_ripemd = 0x14;
|
||||
}
|
||||
|
||||
|
||||
message FingerprintGrainChallenge {
|
||||
required bytes kek = 0xa;
|
||||
}
|
||||
|
||||
|
||||
message FingerprintHmacRipemdChallenge {
|
||||
required bytes challenge = 0xa;
|
||||
}
|
||||
|
||||
|
||||
message PoWChallengeUnion {
|
||||
optional PoWHashCashChallenge hash_cash = 0xa;
|
||||
}
|
||||
|
||||
message PoWHashCashChallenge {
|
||||
optional bytes prefix = 0xa;
|
||||
optional int32 length = 0x14;
|
||||
optional int32 target = 0x1e;
|
||||
}
|
||||
|
||||
|
||||
message CryptoChallengeUnion {
|
||||
optional CryptoShannonChallenge shannon = 0xa;
|
||||
optional CryptoRc4Sha1HmacChallenge rc4_sha1_hmac = 0x14;
|
||||
}
|
||||
|
||||
|
||||
message CryptoShannonChallenge {
|
||||
}
|
||||
|
||||
|
||||
message CryptoRc4Sha1HmacChallenge {
|
||||
}
|
||||
|
||||
|
||||
message UpgradeRequiredMessage {
|
||||
required bytes upgrade_signed_part = 0xa;
|
||||
required bytes signature = 0x14;
|
||||
optional string http_suffix = 0x1e;
|
||||
}
|
||||
|
||||
message APLoginFailed {
|
||||
required ErrorCode error_code = 0xa;
|
||||
optional int32 retry_delay = 0x14;
|
||||
optional int32 expiry = 0x1e;
|
||||
optional string error_description = 0x28;
|
||||
}
|
||||
|
||||
enum ErrorCode {
|
||||
ProtocolError = 0x0;
|
||||
TryAnotherAP = 0x2;
|
||||
BadConnectionId = 0x5;
|
||||
TravelRestriction = 0x9;
|
||||
PremiumAccountRequired = 0xb;
|
||||
BadCredentials = 0xc;
|
||||
CouldNotValidateCredentials = 0xd;
|
||||
AccountExists = 0xe;
|
||||
ExtraVerificationRequired = 0xf;
|
||||
InvalidAppKey = 0x10;
|
||||
ApplicationBanned = 0x11;
|
||||
}
|
||||
|
||||
message ClientResponsePlaintext {
|
||||
required LoginCryptoResponseUnion login_crypto_response = 0xa;
|
||||
required PoWResponseUnion pow_response = 0x14;
|
||||
required CryptoResponseUnion crypto_response = 0x1e;
|
||||
}
|
||||
|
||||
|
||||
message LoginCryptoResponseUnion {
|
||||
optional LoginCryptoDiffieHellmanResponse diffie_hellman = 0xa;
|
||||
}
|
||||
|
||||
|
||||
message LoginCryptoDiffieHellmanResponse {
|
||||
required bytes hmac = 0xa;
|
||||
}
|
||||
|
||||
|
||||
message PoWResponseUnion {
|
||||
optional PoWHashCashResponse hash_cash = 0xa;
|
||||
}
|
||||
|
||||
|
||||
message PoWHashCashResponse {
|
||||
required bytes hash_suffix = 0xa;
|
||||
}
|
||||
|
||||
|
||||
message CryptoResponseUnion {
|
||||
optional CryptoShannonResponse shannon = 0xa;
|
||||
optional CryptoRc4Sha1HmacResponse rc4_sha1_hmac = 0x14;
|
||||
}
|
||||
|
||||
|
||||
message CryptoShannonResponse {
|
||||
optional int32 dummy = 0x1;
|
||||
}
|
||||
|
||||
|
||||
message CryptoRc4Sha1HmacResponse {
|
||||
optional int32 dummy = 0x1;
|
||||
}
|
||||
|
||||
720
src/inputs/librespot-c/src/proto/mercury.pb-c.c
Normal file
720
src/inputs/librespot-c/src/proto/mercury.pb-c.c
Normal file
@@ -0,0 +1,720 @@
|
||||
/* Generated by the protocol buffer compiler. DO NOT EDIT! */
|
||||
/* Generated from: proto/mercury.proto */
|
||||
|
||||
/* Do not generate deprecated warnings for self */
|
||||
#ifndef PROTOBUF_C__NO_DEPRECATED
|
||||
#define PROTOBUF_C__NO_DEPRECATED
|
||||
#endif
|
||||
|
||||
#include "mercury.pb-c.h"
|
||||
void mercury_multi_get_request__init
|
||||
(MercuryMultiGetRequest *message)
|
||||
{
|
||||
static const MercuryMultiGetRequest init_value = MERCURY_MULTI_GET_REQUEST__INIT;
|
||||
*message = init_value;
|
||||
}
|
||||
size_t mercury_multi_get_request__get_packed_size
|
||||
(const MercuryMultiGetRequest *message)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_multi_get_request__descriptor);
|
||||
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
|
||||
}
|
||||
size_t mercury_multi_get_request__pack
|
||||
(const MercuryMultiGetRequest *message,
|
||||
uint8_t *out)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_multi_get_request__descriptor);
|
||||
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
|
||||
}
|
||||
size_t mercury_multi_get_request__pack_to_buffer
|
||||
(const MercuryMultiGetRequest *message,
|
||||
ProtobufCBuffer *buffer)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_multi_get_request__descriptor);
|
||||
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
|
||||
}
|
||||
MercuryMultiGetRequest *
|
||||
mercury_multi_get_request__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data)
|
||||
{
|
||||
return (MercuryMultiGetRequest *)
|
||||
protobuf_c_message_unpack (&mercury_multi_get_request__descriptor,
|
||||
allocator, len, data);
|
||||
}
|
||||
void mercury_multi_get_request__free_unpacked
|
||||
(MercuryMultiGetRequest *message,
|
||||
ProtobufCAllocator *allocator)
|
||||
{
|
||||
if(!message)
|
||||
return;
|
||||
assert(message->base.descriptor == &mercury_multi_get_request__descriptor);
|
||||
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
|
||||
}
|
||||
void mercury_multi_get_reply__init
|
||||
(MercuryMultiGetReply *message)
|
||||
{
|
||||
static const MercuryMultiGetReply init_value = MERCURY_MULTI_GET_REPLY__INIT;
|
||||
*message = init_value;
|
||||
}
|
||||
size_t mercury_multi_get_reply__get_packed_size
|
||||
(const MercuryMultiGetReply *message)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_multi_get_reply__descriptor);
|
||||
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
|
||||
}
|
||||
size_t mercury_multi_get_reply__pack
|
||||
(const MercuryMultiGetReply *message,
|
||||
uint8_t *out)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_multi_get_reply__descriptor);
|
||||
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
|
||||
}
|
||||
size_t mercury_multi_get_reply__pack_to_buffer
|
||||
(const MercuryMultiGetReply *message,
|
||||
ProtobufCBuffer *buffer)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_multi_get_reply__descriptor);
|
||||
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
|
||||
}
|
||||
MercuryMultiGetReply *
|
||||
mercury_multi_get_reply__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data)
|
||||
{
|
||||
return (MercuryMultiGetReply *)
|
||||
protobuf_c_message_unpack (&mercury_multi_get_reply__descriptor,
|
||||
allocator, len, data);
|
||||
}
|
||||
void mercury_multi_get_reply__free_unpacked
|
||||
(MercuryMultiGetReply *message,
|
||||
ProtobufCAllocator *allocator)
|
||||
{
|
||||
if(!message)
|
||||
return;
|
||||
assert(message->base.descriptor == &mercury_multi_get_reply__descriptor);
|
||||
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
|
||||
}
|
||||
void mercury_request__init
|
||||
(MercuryRequest *message)
|
||||
{
|
||||
static const MercuryRequest init_value = MERCURY_REQUEST__INIT;
|
||||
*message = init_value;
|
||||
}
|
||||
size_t mercury_request__get_packed_size
|
||||
(const MercuryRequest *message)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_request__descriptor);
|
||||
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
|
||||
}
|
||||
size_t mercury_request__pack
|
||||
(const MercuryRequest *message,
|
||||
uint8_t *out)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_request__descriptor);
|
||||
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
|
||||
}
|
||||
size_t mercury_request__pack_to_buffer
|
||||
(const MercuryRequest *message,
|
||||
ProtobufCBuffer *buffer)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_request__descriptor);
|
||||
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
|
||||
}
|
||||
MercuryRequest *
|
||||
mercury_request__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data)
|
||||
{
|
||||
return (MercuryRequest *)
|
||||
protobuf_c_message_unpack (&mercury_request__descriptor,
|
||||
allocator, len, data);
|
||||
}
|
||||
void mercury_request__free_unpacked
|
||||
(MercuryRequest *message,
|
||||
ProtobufCAllocator *allocator)
|
||||
{
|
||||
if(!message)
|
||||
return;
|
||||
assert(message->base.descriptor == &mercury_request__descriptor);
|
||||
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
|
||||
}
|
||||
void mercury_reply__init
|
||||
(MercuryReply *message)
|
||||
{
|
||||
static const MercuryReply init_value = MERCURY_REPLY__INIT;
|
||||
*message = init_value;
|
||||
}
|
||||
size_t mercury_reply__get_packed_size
|
||||
(const MercuryReply *message)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_reply__descriptor);
|
||||
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
|
||||
}
|
||||
size_t mercury_reply__pack
|
||||
(const MercuryReply *message,
|
||||
uint8_t *out)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_reply__descriptor);
|
||||
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
|
||||
}
|
||||
size_t mercury_reply__pack_to_buffer
|
||||
(const MercuryReply *message,
|
||||
ProtobufCBuffer *buffer)
|
||||
{
|
||||
assert(message->base.descriptor == &mercury_reply__descriptor);
|
||||
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
|
||||
}
|
||||
MercuryReply *
|
||||
mercury_reply__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data)
|
||||
{
|
||||
return (MercuryReply *)
|
||||
protobuf_c_message_unpack (&mercury_reply__descriptor,
|
||||
allocator, len, data);
|
||||
}
|
||||
void mercury_reply__free_unpacked
|
||||
(MercuryReply *message,
|
||||
ProtobufCAllocator *allocator)
|
||||
{
|
||||
if(!message)
|
||||
return;
|
||||
assert(message->base.descriptor == &mercury_reply__descriptor);
|
||||
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
|
||||
}
|
||||
void header__init
|
||||
(Header *message)
|
||||
{
|
||||
static const Header init_value = HEADER__INIT;
|
||||
*message = init_value;
|
||||
}
|
||||
size_t header__get_packed_size
|
||||
(const Header *message)
|
||||
{
|
||||
assert(message->base.descriptor == &header__descriptor);
|
||||
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
|
||||
}
|
||||
size_t header__pack
|
||||
(const Header *message,
|
||||
uint8_t *out)
|
||||
{
|
||||
assert(message->base.descriptor == &header__descriptor);
|
||||
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
|
||||
}
|
||||
size_t header__pack_to_buffer
|
||||
(const Header *message,
|
||||
ProtobufCBuffer *buffer)
|
||||
{
|
||||
assert(message->base.descriptor == &header__descriptor);
|
||||
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
|
||||
}
|
||||
Header *
|
||||
header__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data)
|
||||
{
|
||||
return (Header *)
|
||||
protobuf_c_message_unpack (&header__descriptor,
|
||||
allocator, len, data);
|
||||
}
|
||||
void header__free_unpacked
|
||||
(Header *message,
|
||||
ProtobufCAllocator *allocator)
|
||||
{
|
||||
if(!message)
|
||||
return;
|
||||
assert(message->base.descriptor == &header__descriptor);
|
||||
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
|
||||
}
|
||||
void user_field__init
|
||||
(UserField *message)
|
||||
{
|
||||
static const UserField init_value = USER_FIELD__INIT;
|
||||
*message = init_value;
|
||||
}
|
||||
size_t user_field__get_packed_size
|
||||
(const UserField *message)
|
||||
{
|
||||
assert(message->base.descriptor == &user_field__descriptor);
|
||||
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
|
||||
}
|
||||
size_t user_field__pack
|
||||
(const UserField *message,
|
||||
uint8_t *out)
|
||||
{
|
||||
assert(message->base.descriptor == &user_field__descriptor);
|
||||
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
|
||||
}
|
||||
size_t user_field__pack_to_buffer
|
||||
(const UserField *message,
|
||||
ProtobufCBuffer *buffer)
|
||||
{
|
||||
assert(message->base.descriptor == &user_field__descriptor);
|
||||
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
|
||||
}
|
||||
UserField *
|
||||
user_field__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data)
|
||||
{
|
||||
return (UserField *)
|
||||
protobuf_c_message_unpack (&user_field__descriptor,
|
||||
allocator, len, data);
|
||||
}
|
||||
void user_field__free_unpacked
|
||||
(UserField *message,
|
||||
ProtobufCAllocator *allocator)
|
||||
{
|
||||
if(!message)
|
||||
return;
|
||||
assert(message->base.descriptor == &user_field__descriptor);
|
||||
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
|
||||
}
|
||||
static const ProtobufCFieldDescriptor mercury_multi_get_request__field_descriptors[1] =
|
||||
{
|
||||
{
|
||||
"request",
|
||||
1,
|
||||
PROTOBUF_C_LABEL_REPEATED,
|
||||
PROTOBUF_C_TYPE_MESSAGE,
|
||||
offsetof(MercuryMultiGetRequest, n_request),
|
||||
offsetof(MercuryMultiGetRequest, request),
|
||||
&mercury_request__descriptor,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
};
|
||||
static const unsigned mercury_multi_get_request__field_indices_by_name[] = {
|
||||
0, /* field[0] = request */
|
||||
};
|
||||
static const ProtobufCIntRange mercury_multi_get_request__number_ranges[1 + 1] =
|
||||
{
|
||||
{ 1, 0 },
|
||||
{ 0, 1 }
|
||||
};
|
||||
const ProtobufCMessageDescriptor mercury_multi_get_request__descriptor =
|
||||
{
|
||||
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
|
||||
"MercuryMultiGetRequest",
|
||||
"MercuryMultiGetRequest",
|
||||
"MercuryMultiGetRequest",
|
||||
"",
|
||||
sizeof(MercuryMultiGetRequest),
|
||||
1,
|
||||
mercury_multi_get_request__field_descriptors,
|
||||
mercury_multi_get_request__field_indices_by_name,
|
||||
1, mercury_multi_get_request__number_ranges,
|
||||
(ProtobufCMessageInit) mercury_multi_get_request__init,
|
||||
NULL,NULL,NULL /* reserved[123] */
|
||||
};
|
||||
static const ProtobufCFieldDescriptor mercury_multi_get_reply__field_descriptors[1] =
|
||||
{
|
||||
{
|
||||
"reply",
|
||||
1,
|
||||
PROTOBUF_C_LABEL_REPEATED,
|
||||
PROTOBUF_C_TYPE_MESSAGE,
|
||||
offsetof(MercuryMultiGetReply, n_reply),
|
||||
offsetof(MercuryMultiGetReply, reply),
|
||||
&mercury_reply__descriptor,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
};
|
||||
static const unsigned mercury_multi_get_reply__field_indices_by_name[] = {
|
||||
0, /* field[0] = reply */
|
||||
};
|
||||
static const ProtobufCIntRange mercury_multi_get_reply__number_ranges[1 + 1] =
|
||||
{
|
||||
{ 1, 0 },
|
||||
{ 0, 1 }
|
||||
};
|
||||
const ProtobufCMessageDescriptor mercury_multi_get_reply__descriptor =
|
||||
{
|
||||
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
|
||||
"MercuryMultiGetReply",
|
||||
"MercuryMultiGetReply",
|
||||
"MercuryMultiGetReply",
|
||||
"",
|
||||
sizeof(MercuryMultiGetReply),
|
||||
1,
|
||||
mercury_multi_get_reply__field_descriptors,
|
||||
mercury_multi_get_reply__field_indices_by_name,
|
||||
1, mercury_multi_get_reply__number_ranges,
|
||||
(ProtobufCMessageInit) mercury_multi_get_reply__init,
|
||||
NULL,NULL,NULL /* reserved[123] */
|
||||
};
|
||||
static const ProtobufCFieldDescriptor mercury_request__field_descriptors[4] =
|
||||
{
|
||||
{
|
||||
"uri",
|
||||
1,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_STRING,
|
||||
0, /* quantifier_offset */
|
||||
offsetof(MercuryRequest, uri),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"content_type",
|
||||
2,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_STRING,
|
||||
0, /* quantifier_offset */
|
||||
offsetof(MercuryRequest, content_type),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"body",
|
||||
3,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_BYTES,
|
||||
offsetof(MercuryRequest, has_body),
|
||||
offsetof(MercuryRequest, body),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"etag",
|
||||
4,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_BYTES,
|
||||
offsetof(MercuryRequest, has_etag),
|
||||
offsetof(MercuryRequest, etag),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
};
|
||||
static const unsigned mercury_request__field_indices_by_name[] = {
|
||||
2, /* field[2] = body */
|
||||
1, /* field[1] = content_type */
|
||||
3, /* field[3] = etag */
|
||||
0, /* field[0] = uri */
|
||||
};
|
||||
static const ProtobufCIntRange mercury_request__number_ranges[1 + 1] =
|
||||
{
|
||||
{ 1, 0 },
|
||||
{ 0, 4 }
|
||||
};
|
||||
const ProtobufCMessageDescriptor mercury_request__descriptor =
|
||||
{
|
||||
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
|
||||
"MercuryRequest",
|
||||
"MercuryRequest",
|
||||
"MercuryRequest",
|
||||
"",
|
||||
sizeof(MercuryRequest),
|
||||
4,
|
||||
mercury_request__field_descriptors,
|
||||
mercury_request__field_indices_by_name,
|
||||
1, mercury_request__number_ranges,
|
||||
(ProtobufCMessageInit) mercury_request__init,
|
||||
NULL,NULL,NULL /* reserved[123] */
|
||||
};
|
||||
static const ProtobufCEnumValue mercury_reply__cache_policy__enum_values_by_number[3] =
|
||||
{
|
||||
{ "CACHE_NO", "MERCURY_REPLY__CACHE_POLICY__CACHE_NO", 1 },
|
||||
{ "CACHE_PRIVATE", "MERCURY_REPLY__CACHE_POLICY__CACHE_PRIVATE", 2 },
|
||||
{ "CACHE_PUBLIC", "MERCURY_REPLY__CACHE_POLICY__CACHE_PUBLIC", 3 },
|
||||
};
|
||||
static const ProtobufCIntRange mercury_reply__cache_policy__value_ranges[] = {
|
||||
{1, 0},{0, 3}
|
||||
};
|
||||
static const ProtobufCEnumValueIndex mercury_reply__cache_policy__enum_values_by_name[3] =
|
||||
{
|
||||
{ "CACHE_NO", 0 },
|
||||
{ "CACHE_PRIVATE", 1 },
|
||||
{ "CACHE_PUBLIC", 2 },
|
||||
};
|
||||
const ProtobufCEnumDescriptor mercury_reply__cache_policy__descriptor =
|
||||
{
|
||||
PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC,
|
||||
"MercuryReply.CachePolicy",
|
||||
"CachePolicy",
|
||||
"MercuryReply__CachePolicy",
|
||||
"",
|
||||
3,
|
||||
mercury_reply__cache_policy__enum_values_by_number,
|
||||
3,
|
||||
mercury_reply__cache_policy__enum_values_by_name,
|
||||
1,
|
||||
mercury_reply__cache_policy__value_ranges,
|
||||
NULL,NULL,NULL,NULL /* reserved[1234] */
|
||||
};
|
||||
static const ProtobufCFieldDescriptor mercury_reply__field_descriptors[7] =
|
||||
{
|
||||
{
|
||||
"status_code",
|
||||
1,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_SINT32,
|
||||
offsetof(MercuryReply, has_status_code),
|
||||
offsetof(MercuryReply, status_code),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"status_message",
|
||||
2,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_STRING,
|
||||
0, /* quantifier_offset */
|
||||
offsetof(MercuryReply, status_message),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"cache_policy",
|
||||
3,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_ENUM,
|
||||
offsetof(MercuryReply, has_cache_policy),
|
||||
offsetof(MercuryReply, cache_policy),
|
||||
&mercury_reply__cache_policy__descriptor,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"ttl",
|
||||
4,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_SINT32,
|
||||
offsetof(MercuryReply, has_ttl),
|
||||
offsetof(MercuryReply, ttl),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"etag",
|
||||
5,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_BYTES,
|
||||
offsetof(MercuryReply, has_etag),
|
||||
offsetof(MercuryReply, etag),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"content_type",
|
||||
6,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_STRING,
|
||||
0, /* quantifier_offset */
|
||||
offsetof(MercuryReply, content_type),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"body",
|
||||
7,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_BYTES,
|
||||
offsetof(MercuryReply, has_body),
|
||||
offsetof(MercuryReply, body),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
};
|
||||
static const unsigned mercury_reply__field_indices_by_name[] = {
|
||||
6, /* field[6] = body */
|
||||
2, /* field[2] = cache_policy */
|
||||
5, /* field[5] = content_type */
|
||||
4, /* field[4] = etag */
|
||||
0, /* field[0] = status_code */
|
||||
1, /* field[1] = status_message */
|
||||
3, /* field[3] = ttl */
|
||||
};
|
||||
static const ProtobufCIntRange mercury_reply__number_ranges[1 + 1] =
|
||||
{
|
||||
{ 1, 0 },
|
||||
{ 0, 7 }
|
||||
};
|
||||
const ProtobufCMessageDescriptor mercury_reply__descriptor =
|
||||
{
|
||||
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
|
||||
"MercuryReply",
|
||||
"MercuryReply",
|
||||
"MercuryReply",
|
||||
"",
|
||||
sizeof(MercuryReply),
|
||||
7,
|
||||
mercury_reply__field_descriptors,
|
||||
mercury_reply__field_indices_by_name,
|
||||
1, mercury_reply__number_ranges,
|
||||
(ProtobufCMessageInit) mercury_reply__init,
|
||||
NULL,NULL,NULL /* reserved[123] */
|
||||
};
|
||||
static const ProtobufCFieldDescriptor header__field_descriptors[5] =
|
||||
{
|
||||
{
|
||||
"uri",
|
||||
1,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_STRING,
|
||||
0, /* quantifier_offset */
|
||||
offsetof(Header, uri),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"content_type",
|
||||
2,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_STRING,
|
||||
0, /* quantifier_offset */
|
||||
offsetof(Header, content_type),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"method",
|
||||
3,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_STRING,
|
||||
0, /* quantifier_offset */
|
||||
offsetof(Header, method),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"status_code",
|
||||
4,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_SINT32,
|
||||
offsetof(Header, has_status_code),
|
||||
offsetof(Header, status_code),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"user_fields",
|
||||
6,
|
||||
PROTOBUF_C_LABEL_REPEATED,
|
||||
PROTOBUF_C_TYPE_MESSAGE,
|
||||
offsetof(Header, n_user_fields),
|
||||
offsetof(Header, user_fields),
|
||||
&user_field__descriptor,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
};
|
||||
static const unsigned header__field_indices_by_name[] = {
|
||||
1, /* field[1] = content_type */
|
||||
2, /* field[2] = method */
|
||||
3, /* field[3] = status_code */
|
||||
0, /* field[0] = uri */
|
||||
4, /* field[4] = user_fields */
|
||||
};
|
||||
static const ProtobufCIntRange header__number_ranges[2 + 1] =
|
||||
{
|
||||
{ 1, 0 },
|
||||
{ 6, 4 },
|
||||
{ 0, 5 }
|
||||
};
|
||||
const ProtobufCMessageDescriptor header__descriptor =
|
||||
{
|
||||
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
|
||||
"Header",
|
||||
"Header",
|
||||
"Header",
|
||||
"",
|
||||
sizeof(Header),
|
||||
5,
|
||||
header__field_descriptors,
|
||||
header__field_indices_by_name,
|
||||
2, header__number_ranges,
|
||||
(ProtobufCMessageInit) header__init,
|
||||
NULL,NULL,NULL /* reserved[123] */
|
||||
};
|
||||
static const ProtobufCFieldDescriptor user_field__field_descriptors[2] =
|
||||
{
|
||||
{
|
||||
"key",
|
||||
1,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_STRING,
|
||||
0, /* quantifier_offset */
|
||||
offsetof(UserField, key),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
{
|
||||
"value",
|
||||
2,
|
||||
PROTOBUF_C_LABEL_OPTIONAL,
|
||||
PROTOBUF_C_TYPE_BYTES,
|
||||
offsetof(UserField, has_value),
|
||||
offsetof(UserField, value),
|
||||
NULL,
|
||||
NULL,
|
||||
0, /* flags */
|
||||
0,NULL,NULL /* reserved1,reserved2, etc */
|
||||
},
|
||||
};
|
||||
static const unsigned user_field__field_indices_by_name[] = {
|
||||
0, /* field[0] = key */
|
||||
1, /* field[1] = value */
|
||||
};
|
||||
static const ProtobufCIntRange user_field__number_ranges[1 + 1] =
|
||||
{
|
||||
{ 1, 0 },
|
||||
{ 0, 2 }
|
||||
};
|
||||
const ProtobufCMessageDescriptor user_field__descriptor =
|
||||
{
|
||||
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
|
||||
"UserField",
|
||||
"UserField",
|
||||
"UserField",
|
||||
"",
|
||||
sizeof(UserField),
|
||||
2,
|
||||
user_field__field_descriptors,
|
||||
user_field__field_indices_by_name,
|
||||
1, user_field__number_ranges,
|
||||
(ProtobufCMessageInit) user_field__init,
|
||||
NULL,NULL,NULL /* reserved[123] */
|
||||
};
|
||||
274
src/inputs/librespot-c/src/proto/mercury.pb-c.h
Normal file
274
src/inputs/librespot-c/src/proto/mercury.pb-c.h
Normal file
@@ -0,0 +1,274 @@
|
||||
/* Generated by the protocol buffer compiler. DO NOT EDIT! */
|
||||
/* Generated from: proto/mercury.proto */
|
||||
|
||||
#ifndef PROTOBUF_C_proto_2fmercury_2eproto__INCLUDED
|
||||
#define PROTOBUF_C_proto_2fmercury_2eproto__INCLUDED
|
||||
|
||||
#include <protobuf-c/protobuf-c.h>
|
||||
|
||||
PROTOBUF_C__BEGIN_DECLS
|
||||
|
||||
#if PROTOBUF_C_VERSION_NUMBER < 1000000
|
||||
# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers.
|
||||
#elif 1003003 < PROTOBUF_C_MIN_COMPILER_VERSION
|
||||
# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c.
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct _MercuryMultiGetRequest MercuryMultiGetRequest;
|
||||
typedef struct _MercuryMultiGetReply MercuryMultiGetReply;
|
||||
typedef struct _MercuryRequest MercuryRequest;
|
||||
typedef struct _MercuryReply MercuryReply;
|
||||
typedef struct _Header Header;
|
||||
typedef struct _UserField UserField;
|
||||
|
||||
|
||||
/* --- enums --- */
|
||||
|
||||
typedef enum _MercuryReply__CachePolicy {
|
||||
MERCURY_REPLY__CACHE_POLICY__CACHE_NO = 1,
|
||||
MERCURY_REPLY__CACHE_POLICY__CACHE_PRIVATE = 2,
|
||||
MERCURY_REPLY__CACHE_POLICY__CACHE_PUBLIC = 3
|
||||
PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(MERCURY_REPLY__CACHE_POLICY)
|
||||
} MercuryReply__CachePolicy;
|
||||
|
||||
/* --- messages --- */
|
||||
|
||||
struct _MercuryMultiGetRequest
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
size_t n_request;
|
||||
MercuryRequest **request;
|
||||
};
|
||||
#define MERCURY_MULTI_GET_REQUEST__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&mercury_multi_get_request__descriptor) \
|
||||
, 0,NULL }
|
||||
|
||||
|
||||
struct _MercuryMultiGetReply
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
size_t n_reply;
|
||||
MercuryReply **reply;
|
||||
};
|
||||
#define MERCURY_MULTI_GET_REPLY__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&mercury_multi_get_reply__descriptor) \
|
||||
, 0,NULL }
|
||||
|
||||
|
||||
struct _MercuryRequest
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
char *uri;
|
||||
char *content_type;
|
||||
protobuf_c_boolean has_body;
|
||||
ProtobufCBinaryData body;
|
||||
protobuf_c_boolean has_etag;
|
||||
ProtobufCBinaryData etag;
|
||||
};
|
||||
#define MERCURY_REQUEST__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&mercury_request__descriptor) \
|
||||
, NULL, NULL, 0, {0,NULL}, 0, {0,NULL} }
|
||||
|
||||
|
||||
struct _MercuryReply
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
protobuf_c_boolean has_status_code;
|
||||
int32_t status_code;
|
||||
char *status_message;
|
||||
protobuf_c_boolean has_cache_policy;
|
||||
MercuryReply__CachePolicy cache_policy;
|
||||
protobuf_c_boolean has_ttl;
|
||||
int32_t ttl;
|
||||
protobuf_c_boolean has_etag;
|
||||
ProtobufCBinaryData etag;
|
||||
char *content_type;
|
||||
protobuf_c_boolean has_body;
|
||||
ProtobufCBinaryData body;
|
||||
};
|
||||
#define MERCURY_REPLY__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&mercury_reply__descriptor) \
|
||||
, 0, 0, NULL, 0, MERCURY_REPLY__CACHE_POLICY__CACHE_NO, 0, 0, 0, {0,NULL}, NULL, 0, {0,NULL} }
|
||||
|
||||
|
||||
struct _Header
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
char *uri;
|
||||
char *content_type;
|
||||
char *method;
|
||||
protobuf_c_boolean has_status_code;
|
||||
int32_t status_code;
|
||||
size_t n_user_fields;
|
||||
UserField **user_fields;
|
||||
};
|
||||
#define HEADER__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&header__descriptor) \
|
||||
, NULL, NULL, NULL, 0, 0, 0,NULL }
|
||||
|
||||
|
||||
struct _UserField
|
||||
{
|
||||
ProtobufCMessage base;
|
||||
char *key;
|
||||
protobuf_c_boolean has_value;
|
||||
ProtobufCBinaryData value;
|
||||
};
|
||||
#define USER_FIELD__INIT \
|
||||
{ PROTOBUF_C_MESSAGE_INIT (&user_field__descriptor) \
|
||||
, NULL, 0, {0,NULL} }
|
||||
|
||||
|
||||
/* MercuryMultiGetRequest methods */
|
||||
void mercury_multi_get_request__init
|
||||
(MercuryMultiGetRequest *message);
|
||||
size_t mercury_multi_get_request__get_packed_size
|
||||
(const MercuryMultiGetRequest *message);
|
||||
size_t mercury_multi_get_request__pack
|
||||
(const MercuryMultiGetRequest *message,
|
||||
uint8_t *out);
|
||||
size_t mercury_multi_get_request__pack_to_buffer
|
||||
(const MercuryMultiGetRequest *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
MercuryMultiGetRequest *
|
||||
mercury_multi_get_request__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void mercury_multi_get_request__free_unpacked
|
||||
(MercuryMultiGetRequest *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* MercuryMultiGetReply methods */
|
||||
void mercury_multi_get_reply__init
|
||||
(MercuryMultiGetReply *message);
|
||||
size_t mercury_multi_get_reply__get_packed_size
|
||||
(const MercuryMultiGetReply *message);
|
||||
size_t mercury_multi_get_reply__pack
|
||||
(const MercuryMultiGetReply *message,
|
||||
uint8_t *out);
|
||||
size_t mercury_multi_get_reply__pack_to_buffer
|
||||
(const MercuryMultiGetReply *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
MercuryMultiGetReply *
|
||||
mercury_multi_get_reply__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void mercury_multi_get_reply__free_unpacked
|
||||
(MercuryMultiGetReply *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* MercuryRequest methods */
|
||||
void mercury_request__init
|
||||
(MercuryRequest *message);
|
||||
size_t mercury_request__get_packed_size
|
||||
(const MercuryRequest *message);
|
||||
size_t mercury_request__pack
|
||||
(const MercuryRequest *message,
|
||||
uint8_t *out);
|
||||
size_t mercury_request__pack_to_buffer
|
||||
(const MercuryRequest *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
MercuryRequest *
|
||||
mercury_request__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void mercury_request__free_unpacked
|
||||
(MercuryRequest *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* MercuryReply methods */
|
||||
void mercury_reply__init
|
||||
(MercuryReply *message);
|
||||
size_t mercury_reply__get_packed_size
|
||||
(const MercuryReply *message);
|
||||
size_t mercury_reply__pack
|
||||
(const MercuryReply *message,
|
||||
uint8_t *out);
|
||||
size_t mercury_reply__pack_to_buffer
|
||||
(const MercuryReply *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
MercuryReply *
|
||||
mercury_reply__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void mercury_reply__free_unpacked
|
||||
(MercuryReply *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* Header methods */
|
||||
void header__init
|
||||
(Header *message);
|
||||
size_t header__get_packed_size
|
||||
(const Header *message);
|
||||
size_t header__pack
|
||||
(const Header *message,
|
||||
uint8_t *out);
|
||||
size_t header__pack_to_buffer
|
||||
(const Header *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
Header *
|
||||
header__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void header__free_unpacked
|
||||
(Header *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* UserField methods */
|
||||
void user_field__init
|
||||
(UserField *message);
|
||||
size_t user_field__get_packed_size
|
||||
(const UserField *message);
|
||||
size_t user_field__pack
|
||||
(const UserField *message,
|
||||
uint8_t *out);
|
||||
size_t user_field__pack_to_buffer
|
||||
(const UserField *message,
|
||||
ProtobufCBuffer *buffer);
|
||||
UserField *
|
||||
user_field__unpack
|
||||
(ProtobufCAllocator *allocator,
|
||||
size_t len,
|
||||
const uint8_t *data);
|
||||
void user_field__free_unpacked
|
||||
(UserField *message,
|
||||
ProtobufCAllocator *allocator);
|
||||
/* --- per-message closures --- */
|
||||
|
||||
typedef void (*MercuryMultiGetRequest_Closure)
|
||||
(const MercuryMultiGetRequest *message,
|
||||
void *closure_data);
|
||||
typedef void (*MercuryMultiGetReply_Closure)
|
||||
(const MercuryMultiGetReply *message,
|
||||
void *closure_data);
|
||||
typedef void (*MercuryRequest_Closure)
|
||||
(const MercuryRequest *message,
|
||||
void *closure_data);
|
||||
typedef void (*MercuryReply_Closure)
|
||||
(const MercuryReply *message,
|
||||
void *closure_data);
|
||||
typedef void (*Header_Closure)
|
||||
(const Header *message,
|
||||
void *closure_data);
|
||||
typedef void (*UserField_Closure)
|
||||
(const UserField *message,
|
||||
void *closure_data);
|
||||
|
||||
/* --- services --- */
|
||||
|
||||
|
||||
/* --- descriptors --- */
|
||||
|
||||
extern const ProtobufCMessageDescriptor mercury_multi_get_request__descriptor;
|
||||
extern const ProtobufCMessageDescriptor mercury_multi_get_reply__descriptor;
|
||||
extern const ProtobufCMessageDescriptor mercury_request__descriptor;
|
||||
extern const ProtobufCMessageDescriptor mercury_reply__descriptor;
|
||||
extern const ProtobufCEnumDescriptor mercury_reply__cache_policy__descriptor;
|
||||
extern const ProtobufCMessageDescriptor header__descriptor;
|
||||
extern const ProtobufCMessageDescriptor user_field__descriptor;
|
||||
|
||||
PROTOBUF_C__END_DECLS
|
||||
|
||||
|
||||
#endif /* PROTOBUF_C_proto_2fmercury_2eproto__INCLUDED */
|
||||
46
src/inputs/librespot-c/src/proto/mercury.proto
Normal file
46
src/inputs/librespot-c/src/proto/mercury.proto
Normal file
@@ -0,0 +1,46 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message MercuryMultiGetRequest {
|
||||
repeated MercuryRequest request = 0x1;
|
||||
}
|
||||
|
||||
message MercuryMultiGetReply {
|
||||
repeated MercuryReply reply = 0x1;
|
||||
}
|
||||
|
||||
message MercuryRequest {
|
||||
optional string uri = 0x1;
|
||||
optional string content_type = 0x2;
|
||||
optional bytes body = 0x3;
|
||||
optional bytes etag = 0x4;
|
||||
}
|
||||
|
||||
message MercuryReply {
|
||||
optional sint32 status_code = 0x1;
|
||||
optional string status_message = 0x2;
|
||||
optional CachePolicy cache_policy = 0x3;
|
||||
enum CachePolicy {
|
||||
CACHE_NO = 0x1;
|
||||
CACHE_PRIVATE = 0x2;
|
||||
CACHE_PUBLIC = 0x3;
|
||||
}
|
||||
optional sint32 ttl = 0x4;
|
||||
optional bytes etag = 0x5;
|
||||
optional string content_type = 0x6;
|
||||
optional bytes body = 0x7;
|
||||
}
|
||||
|
||||
|
||||
message Header {
|
||||
optional string uri = 0x01;
|
||||
optional string content_type = 0x02;
|
||||
optional string method = 0x03;
|
||||
optional sint32 status_code = 0x04;
|
||||
repeated UserField user_fields = 0x06;
|
||||
}
|
||||
|
||||
message UserField {
|
||||
optional string key = 0x01;
|
||||
optional bytes value = 0x02;
|
||||
}
|
||||
|
||||
10
src/inputs/librespot-c/src/proto/mergedprofile.proto
Normal file
10
src/inputs/librespot-c/src/proto/mergedprofile.proto
Normal file
@@ -0,0 +1,10 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message MergedProfileRequest {
|
||||
}
|
||||
|
||||
message MergedProfileReply {
|
||||
optional string username = 0x1;
|
||||
optional string artistid = 0x2;
|
||||
}
|
||||
|
||||
3604
src/inputs/librespot-c/src/proto/metadata.pb-c.c
Normal file
3604
src/inputs/librespot-c/src/proto/metadata.pb-c.c
Normal file
File diff suppressed because it is too large
Load Diff
1084
src/inputs/librespot-c/src/proto/metadata.pb-c.h
Normal file
1084
src/inputs/librespot-c/src/proto/metadata.pb-c.h
Normal file
File diff suppressed because it is too large
Load Diff
266
src/inputs/librespot-c/src/proto/metadata.proto
Normal file
266
src/inputs/librespot-c/src/proto/metadata.proto
Normal file
@@ -0,0 +1,266 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message TopTracks {
|
||||
optional string country = 0x1;
|
||||
repeated Track track = 0x2;
|
||||
}
|
||||
|
||||
message ActivityPeriod {
|
||||
optional sint32 start_year = 0x1;
|
||||
optional sint32 end_year = 0x2;
|
||||
optional sint32 decade = 0x3;
|
||||
}
|
||||
|
||||
message Artist {
|
||||
optional bytes gid = 0x1;
|
||||
optional string name = 0x2;
|
||||
optional sint32 popularity = 0x3;
|
||||
repeated TopTracks top_track = 0x4;
|
||||
repeated AlbumGroup album_group = 0x5;
|
||||
repeated AlbumGroup single_group = 0x6;
|
||||
repeated AlbumGroup compilation_group = 0x7;
|
||||
repeated AlbumGroup appears_on_group = 0x8;
|
||||
repeated string genre = 0x9;
|
||||
repeated ExternalId external_id = 0xa;
|
||||
repeated Image portrait = 0xb;
|
||||
repeated Biography biography = 0xc;
|
||||
repeated ActivityPeriod activity_period = 0xd;
|
||||
repeated Restriction restriction = 0xe;
|
||||
repeated Artist related = 0xf;
|
||||
optional bool is_portrait_album_cover = 0x10;
|
||||
optional ImageGroup portrait_group = 0x11;
|
||||
}
|
||||
|
||||
message AlbumGroup {
|
||||
repeated Album album = 0x1;
|
||||
}
|
||||
|
||||
message Date {
|
||||
optional sint32 year = 0x1;
|
||||
optional sint32 month = 0x2;
|
||||
optional sint32 day = 0x3;
|
||||
optional sint32 hour = 0x4;
|
||||
optional sint32 minute = 0x5;
|
||||
}
|
||||
|
||||
message Album {
|
||||
optional bytes gid = 0x1;
|
||||
optional string name = 0x2;
|
||||
repeated Artist artist = 0x3;
|
||||
optional Type typ = 0x4;
|
||||
enum Type {
|
||||
ALBUM = 0x1;
|
||||
SINGLE = 0x2;
|
||||
COMPILATION = 0x3;
|
||||
EP = 0x4;
|
||||
}
|
||||
optional string label = 0x5;
|
||||
optional Date date = 0x6;
|
||||
optional sint32 popularity = 0x7;
|
||||
repeated string genre = 0x8;
|
||||
repeated Image cover = 0x9;
|
||||
repeated ExternalId external_id = 0xa;
|
||||
repeated Disc disc = 0xb;
|
||||
repeated string review = 0xc;
|
||||
repeated Copyright copyright = 0xd;
|
||||
repeated Restriction restriction = 0xe;
|
||||
repeated Album related = 0xf;
|
||||
repeated SalePeriod sale_period = 0x10;
|
||||
optional ImageGroup cover_group = 0x11;
|
||||
}
|
||||
|
||||
message Track {
|
||||
optional bytes gid = 0x1;
|
||||
optional string name = 0x2;
|
||||
optional Album album = 0x3;
|
||||
repeated Artist artist = 0x4;
|
||||
optional sint32 number = 0x5;
|
||||
optional sint32 disc_number = 0x6;
|
||||
optional sint32 duration = 0x7;
|
||||
optional sint32 popularity = 0x8;
|
||||
optional bool explicit = 0x9;
|
||||
repeated ExternalId external_id = 0xa;
|
||||
repeated Restriction restriction = 0xb;
|
||||
repeated AudioFile file = 0xc;
|
||||
repeated Track alternative = 0xd;
|
||||
repeated SalePeriod sale_period = 0xe;
|
||||
repeated AudioFile preview = 0xf;
|
||||
}
|
||||
|
||||
message Image {
|
||||
optional bytes file_id = 0x1;
|
||||
optional Size size = 0x2;
|
||||
enum Size {
|
||||
DEFAULT = 0x0;
|
||||
SMALL = 0x1;
|
||||
LARGE = 0x2;
|
||||
XLARGE = 0x3;
|
||||
}
|
||||
optional sint32 width = 0x3;
|
||||
optional sint32 height = 0x4;
|
||||
}
|
||||
|
||||
message ImageGroup {
|
||||
repeated Image image = 0x1;
|
||||
}
|
||||
|
||||
message Biography {
|
||||
optional string text = 0x1;
|
||||
repeated Image portrait = 0x2;
|
||||
repeated ImageGroup portrait_group = 0x3;
|
||||
}
|
||||
|
||||
message Disc {
|
||||
optional sint32 number = 0x1;
|
||||
optional string name = 0x2;
|
||||
repeated Track track = 0x3;
|
||||
}
|
||||
|
||||
message Copyright {
|
||||
optional Type typ = 0x1;
|
||||
enum Type {
|
||||
P = 0x0;
|
||||
C = 0x1;
|
||||
}
|
||||
optional string text = 0x2;
|
||||
}
|
||||
|
||||
message Restriction {
|
||||
enum Catalogue {
|
||||
AD = 0;
|
||||
SUBSCRIPTION = 1;
|
||||
CATALOGUE_ALL = 2;
|
||||
SHUFFLE = 3;
|
||||
COMMERCIAL = 4;
|
||||
}
|
||||
enum Type {
|
||||
STREAMING = 0x0;
|
||||
}
|
||||
repeated Catalogue catalogue = 0x1;
|
||||
optional string countries_allowed = 0x2;
|
||||
optional string countries_forbidden = 0x3;
|
||||
optional Type typ = 0x4;
|
||||
|
||||
repeated string catalogue_str = 0x5;
|
||||
}
|
||||
|
||||
message Availability {
|
||||
repeated string catalogue_str = 0x1;
|
||||
optional Date start = 0x2;
|
||||
}
|
||||
|
||||
message SalePeriod {
|
||||
repeated Restriction restriction = 0x1;
|
||||
optional Date start = 0x2;
|
||||
optional Date end = 0x3;
|
||||
}
|
||||
|
||||
message ExternalId {
|
||||
optional string typ = 0x1;
|
||||
optional string id = 0x2;
|
||||
}
|
||||
|
||||
message AudioFile {
|
||||
optional bytes file_id = 0x1;
|
||||
optional Format format = 0x2;
|
||||
enum Format {
|
||||
OGG_VORBIS_96 = 0x0;
|
||||
OGG_VORBIS_160 = 0x1;
|
||||
OGG_VORBIS_320 = 0x2;
|
||||
MP3_256 = 0x3;
|
||||
MP3_320 = 0x4;
|
||||
MP3_160 = 0x5;
|
||||
MP3_96 = 0x6;
|
||||
MP3_160_ENC = 0x7;
|
||||
// v4
|
||||
// AAC_24 = 0x8;
|
||||
// AAC_48 = 0x9;
|
||||
MP4_128_DUAL = 0x8;
|
||||
OTHER3 = 0x9;
|
||||
AAC_160 = 0xa;
|
||||
AAC_320 = 0xb;
|
||||
MP4_128 = 0xc;
|
||||
OTHER5 = 0xd;
|
||||
}
|
||||
}
|
||||
|
||||
message VideoFile {
|
||||
optional bytes file_id = 1;
|
||||
}
|
||||
|
||||
// Podcast Protos
|
||||
message Show {
|
||||
enum MediaType {
|
||||
MIXED = 0;
|
||||
AUDIO = 1;
|
||||
VIDEO = 2;
|
||||
}
|
||||
enum ConsumptionOrder {
|
||||
SEQUENTIAL = 1;
|
||||
EPISODIC = 2;
|
||||
RECENT = 3;
|
||||
}
|
||||
enum PassthroughEnum {
|
||||
UNKNOWN = 0;
|
||||
NONE = 1;
|
||||
ALLOWED = 2;
|
||||
}
|
||||
optional bytes gid = 0x1;
|
||||
optional string name = 0x2;
|
||||
optional string description = 0x40;
|
||||
optional sint32 deprecated_popularity = 0x41;
|
||||
optional string publisher = 0x42;
|
||||
optional string language = 0x43;
|
||||
optional bool explicit = 0x44;
|
||||
optional ImageGroup covers = 0x45;
|
||||
repeated Episode episode = 0x46;
|
||||
repeated Copyright copyright = 0x47;
|
||||
repeated Restriction restriction = 0x48;
|
||||
repeated string keyword = 0x49;
|
||||
optional MediaType media_type = 0x4A;
|
||||
optional ConsumptionOrder consumption_order = 0x4B;
|
||||
optional bool interpret_restriction_using_geoip = 0x4C;
|
||||
repeated Availability availability = 0x4E;
|
||||
optional string country_of_origin = 0x4F;
|
||||
repeated Category categories = 0x50;
|
||||
optional PassthroughEnum passthrough = 0x51;
|
||||
}
|
||||
|
||||
message Episode {
|
||||
optional bytes gid = 0x1;
|
||||
optional string name = 0x2;
|
||||
optional sint32 duration = 0x7;
|
||||
optional sint32 popularity = 0x8;
|
||||
repeated AudioFile file = 0xc;
|
||||
optional string description = 0x40;
|
||||
optional sint32 number = 0x41;
|
||||
optional Date publish_time = 0x42;
|
||||
optional sint32 deprecated_popularity = 0x43;
|
||||
optional ImageGroup covers = 0x44;
|
||||
optional string language = 0x45;
|
||||
optional bool explicit = 0x46;
|
||||
optional Show show = 0x47;
|
||||
repeated VideoFile video = 0x48;
|
||||
repeated VideoFile video_preview = 0x49;
|
||||
repeated AudioFile audio_preview = 0x4A;
|
||||
repeated Restriction restriction = 0x4B;
|
||||
optional ImageGroup freeze_frame = 0x4C;
|
||||
repeated string keyword = 0x4D;
|
||||
// Order of these two flags might be wrong!
|
||||
optional bool suppress_monetization = 0x4E;
|
||||
optional bool interpret_restriction_using_geoip = 0x4F;
|
||||
|
||||
optional bool allow_background_playback = 0x51;
|
||||
repeated Availability availability = 0x52;
|
||||
optional string external_url = 0x53;
|
||||
optional OriginalAudio original_audio = 0x54;
|
||||
}
|
||||
|
||||
message Category {
|
||||
optional string name = 0x1;
|
||||
repeated Category subcategories = 0x2;
|
||||
}
|
||||
|
||||
message OriginalAudio {
|
||||
optional bytes uuid = 0x1;
|
||||
}
|
||||
87
src/inputs/librespot-c/src/proto/playlist4changes.proto
Normal file
87
src/inputs/librespot-c/src/proto/playlist4changes.proto
Normal file
@@ -0,0 +1,87 @@
|
||||
syntax = "proto2";
|
||||
|
||||
import "playlist4ops.proto";
|
||||
import "playlist4meta.proto";
|
||||
import "playlist4content.proto";
|
||||
import "playlist4issues.proto";
|
||||
|
||||
message ChangeInfo {
|
||||
optional string user = 0x1;
|
||||
optional int32 timestamp = 0x2;
|
||||
optional bool admin = 0x3;
|
||||
optional bool undo = 0x4;
|
||||
optional bool redo = 0x5;
|
||||
optional bool merge = 0x6;
|
||||
optional bool compressed = 0x7;
|
||||
optional bool migration = 0x8;
|
||||
}
|
||||
|
||||
message Delta {
|
||||
optional bytes base_version = 0x1;
|
||||
repeated Op ops = 0x2;
|
||||
optional ChangeInfo info = 0x4;
|
||||
}
|
||||
|
||||
message Merge {
|
||||
optional bytes base_version = 0x1;
|
||||
optional bytes merge_version = 0x2;
|
||||
optional ChangeInfo info = 0x4;
|
||||
}
|
||||
|
||||
message ChangeSet {
|
||||
optional Kind kind = 0x1;
|
||||
enum Kind {
|
||||
KIND_UNKNOWN = 0x0;
|
||||
DELTA = 0x2;
|
||||
MERGE = 0x3;
|
||||
}
|
||||
optional Delta delta = 0x2;
|
||||
optional Merge merge = 0x3;
|
||||
}
|
||||
|
||||
message RevisionTaggedChangeSet {
|
||||
optional bytes revision = 0x1;
|
||||
optional ChangeSet change_set = 0x2;
|
||||
}
|
||||
|
||||
message Diff {
|
||||
optional bytes from_revision = 0x1;
|
||||
repeated Op ops = 0x2;
|
||||
optional bytes to_revision = 0x3;
|
||||
}
|
||||
|
||||
message ListDump {
|
||||
optional bytes latestRevision = 0x1;
|
||||
optional int32 length = 0x2;
|
||||
optional ListAttributes attributes = 0x3;
|
||||
optional ListChecksum checksum = 0x4;
|
||||
optional ListItems contents = 0x5;
|
||||
repeated Delta pendingDeltas = 0x7;
|
||||
}
|
||||
|
||||
message ListChanges {
|
||||
optional bytes baseRevision = 0x1;
|
||||
repeated Delta deltas = 0x2;
|
||||
optional bool wantResultingRevisions = 0x3;
|
||||
optional bool wantSyncResult = 0x4;
|
||||
optional ListDump dump = 0x5;
|
||||
repeated int32 nonces = 0x6;
|
||||
}
|
||||
|
||||
message SelectedListContent {
|
||||
optional bytes revision = 0x1;
|
||||
optional int32 length = 0x2;
|
||||
optional ListAttributes attributes = 0x3;
|
||||
optional ListChecksum checksum = 0x4;
|
||||
optional ListItems contents = 0x5;
|
||||
optional Diff diff = 0x6;
|
||||
optional Diff syncResult = 0x7;
|
||||
repeated bytes resultingRevisions = 0x8;
|
||||
optional bool multipleHeads = 0x9;
|
||||
optional bool upToDate = 0xa;
|
||||
repeated ClientResolveAction resolveAction = 0xc;
|
||||
repeated ClientIssue issues = 0xd;
|
||||
repeated int32 nonces = 0xe;
|
||||
optional string owner_username =0x10;
|
||||
}
|
||||
|
||||
37
src/inputs/librespot-c/src/proto/playlist4content.proto
Normal file
37
src/inputs/librespot-c/src/proto/playlist4content.proto
Normal file
@@ -0,0 +1,37 @@
|
||||
syntax = "proto2";
|
||||
|
||||
import "playlist4meta.proto";
|
||||
import "playlist4issues.proto";
|
||||
|
||||
message Item {
|
||||
optional string uri = 0x1;
|
||||
optional ItemAttributes attributes = 0x2;
|
||||
}
|
||||
|
||||
message ListItems {
|
||||
optional int32 pos = 0x1;
|
||||
optional bool truncated = 0x2;
|
||||
repeated Item items = 0x3;
|
||||
}
|
||||
|
||||
message ContentRange {
|
||||
optional int32 pos = 0x1;
|
||||
optional int32 length = 0x2;
|
||||
}
|
||||
|
||||
message ListContentSelection {
|
||||
optional bool wantRevision = 0x1;
|
||||
optional bool wantLength = 0x2;
|
||||
optional bool wantAttributes = 0x3;
|
||||
optional bool wantChecksum = 0x4;
|
||||
optional bool wantContent = 0x5;
|
||||
optional ContentRange contentRange = 0x6;
|
||||
optional bool wantDiff = 0x7;
|
||||
optional bytes baseRevision = 0x8;
|
||||
optional bytes hintRevision = 0x9;
|
||||
optional bool wantNothingIfUpToDate = 0xa;
|
||||
optional bool wantResolveAction = 0xc;
|
||||
repeated ClientIssue issues = 0xd;
|
||||
repeated ClientResolveAction resolveAction = 0xe;
|
||||
}
|
||||
|
||||
43
src/inputs/librespot-c/src/proto/playlist4issues.proto
Normal file
43
src/inputs/librespot-c/src/proto/playlist4issues.proto
Normal file
@@ -0,0 +1,43 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message ClientIssue {
|
||||
optional Level level = 0x1;
|
||||
enum Level {
|
||||
LEVEL_UNKNOWN = 0x0;
|
||||
LEVEL_DEBUG = 0x1;
|
||||
LEVEL_INFO = 0x2;
|
||||
LEVEL_NOTICE = 0x3;
|
||||
LEVEL_WARNING = 0x4;
|
||||
LEVEL_ERROR = 0x5;
|
||||
}
|
||||
optional Code code = 0x2;
|
||||
enum Code {
|
||||
CODE_UNKNOWN = 0x0;
|
||||
CODE_INDEX_OUT_OF_BOUNDS = 0x1;
|
||||
CODE_VERSION_MISMATCH = 0x2;
|
||||
CODE_CACHED_CHANGE = 0x3;
|
||||
CODE_OFFLINE_CHANGE = 0x4;
|
||||
CODE_CONCURRENT_CHANGE = 0x5;
|
||||
}
|
||||
optional int32 repeatCount = 0x3;
|
||||
}
|
||||
|
||||
message ClientResolveAction {
|
||||
optional Code code = 0x1;
|
||||
enum Code {
|
||||
CODE_UNKNOWN = 0x0;
|
||||
CODE_NO_ACTION = 0x1;
|
||||
CODE_RETRY = 0x2;
|
||||
CODE_RELOAD = 0x3;
|
||||
CODE_DISCARD_LOCAL_CHANGES = 0x4;
|
||||
CODE_SEND_DUMP = 0x5;
|
||||
CODE_DISPLAY_ERROR_MESSAGE = 0x6;
|
||||
}
|
||||
optional Initiator initiator = 0x2;
|
||||
enum Initiator {
|
||||
INITIATOR_UNKNOWN = 0x0;
|
||||
INITIATOR_SERVER = 0x1;
|
||||
INITIATOR_CLIENT = 0x2;
|
||||
}
|
||||
}
|
||||
|
||||
52
src/inputs/librespot-c/src/proto/playlist4meta.proto
Normal file
52
src/inputs/librespot-c/src/proto/playlist4meta.proto
Normal file
@@ -0,0 +1,52 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message ListChecksum {
|
||||
optional int32 version = 0x1;
|
||||
optional bytes sha1 = 0x4;
|
||||
}
|
||||
|
||||
message DownloadFormat {
|
||||
optional Codec codec = 0x1;
|
||||
enum Codec {
|
||||
CODEC_UNKNOWN = 0x0;
|
||||
OGG_VORBIS = 0x1;
|
||||
FLAC = 0x2;
|
||||
MPEG_1_LAYER_3 = 0x3;
|
||||
}
|
||||
}
|
||||
|
||||
message ListAttributes {
|
||||
optional string name = 0x1;
|
||||
optional string description = 0x2;
|
||||
optional bytes picture = 0x3;
|
||||
optional bool collaborative = 0x4;
|
||||
optional string pl3_version = 0x5;
|
||||
optional bool deleted_by_owner = 0x6;
|
||||
optional bool restricted_collaborative = 0x7;
|
||||
optional int64 deprecated_client_id = 0x8;
|
||||
optional bool public_starred = 0x9;
|
||||
optional string client_id = 0xa;
|
||||
}
|
||||
|
||||
message ItemAttributes {
|
||||
optional string added_by = 0x1;
|
||||
optional int64 timestamp = 0x2;
|
||||
optional string message = 0x3;
|
||||
optional bool seen = 0x4;
|
||||
optional int64 download_count = 0x5;
|
||||
optional DownloadFormat download_format = 0x6;
|
||||
optional string sevendigital_id = 0x7;
|
||||
optional int64 sevendigital_left = 0x8;
|
||||
optional int64 seen_at = 0x9;
|
||||
optional bool public = 0xa;
|
||||
}
|
||||
|
||||
message StringAttribute {
|
||||
optional string key = 0x1;
|
||||
optional string value = 0x2;
|
||||
}
|
||||
|
||||
message StringAttributes {
|
||||
repeated StringAttribute attribute = 0x1;
|
||||
}
|
||||
|
||||
103
src/inputs/librespot-c/src/proto/playlist4ops.proto
Normal file
103
src/inputs/librespot-c/src/proto/playlist4ops.proto
Normal file
@@ -0,0 +1,103 @@
|
||||
syntax = "proto2";
|
||||
|
||||
import "playlist4meta.proto";
|
||||
import "playlist4content.proto";
|
||||
|
||||
message Add {
|
||||
optional int32 fromIndex = 0x1;
|
||||
repeated Item items = 0x2;
|
||||
optional ListChecksum list_checksum = 0x3;
|
||||
optional bool addLast = 0x4;
|
||||
optional bool addFirst = 0x5;
|
||||
}
|
||||
|
||||
message Rem {
|
||||
optional int32 fromIndex = 0x1;
|
||||
optional int32 length = 0x2;
|
||||
repeated Item items = 0x3;
|
||||
optional ListChecksum list_checksum = 0x4;
|
||||
optional ListChecksum items_checksum = 0x5;
|
||||
optional ListChecksum uris_checksum = 0x6;
|
||||
optional bool itemsAsKey = 0x7;
|
||||
}
|
||||
|
||||
message Mov {
|
||||
optional int32 fromIndex = 0x1;
|
||||
optional int32 length = 0x2;
|
||||
optional int32 toIndex = 0x3;
|
||||
optional ListChecksum list_checksum = 0x4;
|
||||
optional ListChecksum items_checksum = 0x5;
|
||||
optional ListChecksum uris_checksum = 0x6;
|
||||
}
|
||||
|
||||
message ItemAttributesPartialState {
|
||||
optional ItemAttributes values = 0x1;
|
||||
repeated ItemAttributeKind no_value = 0x2;
|
||||
|
||||
enum ItemAttributeKind {
|
||||
ITEM_UNKNOWN = 0x0;
|
||||
ITEM_ADDED_BY = 0x1;
|
||||
ITEM_TIMESTAMP = 0x2;
|
||||
ITEM_MESSAGE = 0x3;
|
||||
ITEM_SEEN = 0x4;
|
||||
ITEM_DOWNLOAD_COUNT = 0x5;
|
||||
ITEM_DOWNLOAD_FORMAT = 0x6;
|
||||
ITEM_SEVENDIGITAL_ID = 0x7;
|
||||
ITEM_SEVENDIGITAL_LEFT = 0x8;
|
||||
ITEM_SEEN_AT = 0x9;
|
||||
ITEM_PUBLIC = 0xa;
|
||||
}
|
||||
}
|
||||
|
||||
message ListAttributesPartialState {
|
||||
optional ListAttributes values = 0x1;
|
||||
repeated ListAttributeKind no_value = 0x2;
|
||||
|
||||
enum ListAttributeKind {
|
||||
LIST_UNKNOWN = 0x0;
|
||||
LIST_NAME = 0x1;
|
||||
LIST_DESCRIPTION = 0x2;
|
||||
LIST_PICTURE = 0x3;
|
||||
LIST_COLLABORATIVE = 0x4;
|
||||
LIST_PL3_VERSION = 0x5;
|
||||
LIST_DELETED_BY_OWNER = 0x6;
|
||||
LIST_RESTRICTED_COLLABORATIVE = 0x7;
|
||||
}
|
||||
}
|
||||
|
||||
message UpdateItemAttributes {
|
||||
optional int32 index = 0x1;
|
||||
optional ItemAttributesPartialState new_attributes = 0x2;
|
||||
optional ItemAttributesPartialState old_attributes = 0x3;
|
||||
optional ListChecksum list_checksum = 0x4;
|
||||
optional ListChecksum old_attributes_checksum = 0x5;
|
||||
}
|
||||
|
||||
message UpdateListAttributes {
|
||||
optional ListAttributesPartialState new_attributes = 0x1;
|
||||
optional ListAttributesPartialState old_attributes = 0x2;
|
||||
optional ListChecksum list_checksum = 0x3;
|
||||
optional ListChecksum old_attributes_checksum = 0x4;
|
||||
}
|
||||
|
||||
message Op {
|
||||
optional Kind kind = 0x1;
|
||||
enum Kind {
|
||||
KIND_UNKNOWN = 0x0;
|
||||
ADD = 0x2;
|
||||
REM = 0x3;
|
||||
MOV = 0x4;
|
||||
UPDATE_ITEM_ATTRIBUTES = 0x5;
|
||||
UPDATE_LIST_ATTRIBUTES = 0x6;
|
||||
}
|
||||
optional Add add = 0x2;
|
||||
optional Rem rem = 0x3;
|
||||
optional Mov mov = 0x4;
|
||||
optional UpdateItemAttributes update_item_attributes = 0x5;
|
||||
optional UpdateListAttributes update_list_attributes = 0x6;
|
||||
}
|
||||
|
||||
message OpList {
|
||||
repeated Op ops = 0x1;
|
||||
}
|
||||
|
||||
13
src/inputs/librespot-c/src/proto/popcount.proto
Normal file
13
src/inputs/librespot-c/src/proto/popcount.proto
Normal file
@@ -0,0 +1,13 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message PopcountRequest {
|
||||
}
|
||||
|
||||
message PopcountResult {
|
||||
optional sint64 count = 0x1;
|
||||
optional bool truncated = 0x2;
|
||||
repeated string user = 0x3;
|
||||
repeated sint64 subscriptionTimestamps = 0x4;
|
||||
repeated sint64 insertionTimestamps = 0x5;
|
||||
}
|
||||
|
||||
94
src/inputs/librespot-c/src/proto/presence.proto
Normal file
94
src/inputs/librespot-c/src/proto/presence.proto
Normal file
@@ -0,0 +1,94 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message PlaylistPublishedState {
|
||||
optional string uri = 0x1;
|
||||
optional int64 timestamp = 0x2;
|
||||
}
|
||||
|
||||
message PlaylistTrackAddedState {
|
||||
optional string playlist_uri = 0x1;
|
||||
optional string track_uri = 0x2;
|
||||
optional int64 timestamp = 0x3;
|
||||
}
|
||||
|
||||
message TrackFinishedPlayingState {
|
||||
optional string uri = 0x1;
|
||||
optional string context_uri = 0x2;
|
||||
optional int64 timestamp = 0x3;
|
||||
optional string referrer_uri = 0x4;
|
||||
}
|
||||
|
||||
message FavoriteAppAddedState {
|
||||
optional string app_uri = 0x1;
|
||||
optional int64 timestamp = 0x2;
|
||||
}
|
||||
|
||||
message TrackStartedPlayingState {
|
||||
optional string uri = 0x1;
|
||||
optional string context_uri = 0x2;
|
||||
optional int64 timestamp = 0x3;
|
||||
optional string referrer_uri = 0x4;
|
||||
}
|
||||
|
||||
message UriSharedState {
|
||||
optional string uri = 0x1;
|
||||
optional string message = 0x2;
|
||||
optional int64 timestamp = 0x3;
|
||||
}
|
||||
|
||||
message ArtistFollowedState {
|
||||
optional string uri = 0x1;
|
||||
optional string artist_name = 0x2;
|
||||
optional string artist_cover_uri = 0x3;
|
||||
optional int64 timestamp = 0x4;
|
||||
}
|
||||
|
||||
message DeviceInformation {
|
||||
optional string os = 0x1;
|
||||
optional string type = 0x2;
|
||||
}
|
||||
|
||||
message GenericPresenceState {
|
||||
optional int32 type = 0x1;
|
||||
optional int64 timestamp = 0x2;
|
||||
optional string item_uri = 0x3;
|
||||
optional string item_name = 0x4;
|
||||
optional string item_image = 0x5;
|
||||
optional string context_uri = 0x6;
|
||||
optional string context_name = 0x7;
|
||||
optional string context_image = 0x8;
|
||||
optional string referrer_uri = 0x9;
|
||||
optional string referrer_name = 0xa;
|
||||
optional string referrer_image = 0xb;
|
||||
optional string message = 0xc;
|
||||
optional DeviceInformation device_information = 0xd;
|
||||
}
|
||||
|
||||
message State {
|
||||
optional int64 timestamp = 0x1;
|
||||
optional Type type = 0x2;
|
||||
enum Type {
|
||||
PLAYLIST_PUBLISHED = 0x1;
|
||||
PLAYLIST_TRACK_ADDED = 0x2;
|
||||
TRACK_FINISHED_PLAYING = 0x3;
|
||||
FAVORITE_APP_ADDED = 0x4;
|
||||
TRACK_STARTED_PLAYING = 0x5;
|
||||
URI_SHARED = 0x6;
|
||||
ARTIST_FOLLOWED = 0x7;
|
||||
GENERIC = 0xb;
|
||||
}
|
||||
optional string uri = 0x3;
|
||||
optional PlaylistPublishedState playlist_published = 0x4;
|
||||
optional PlaylistTrackAddedState playlist_track_added = 0x5;
|
||||
optional TrackFinishedPlayingState track_finished_playing = 0x6;
|
||||
optional FavoriteAppAddedState favorite_app_added = 0x7;
|
||||
optional TrackStartedPlayingState track_started_playing = 0x8;
|
||||
optional UriSharedState uri_shared = 0x9;
|
||||
optional ArtistFollowedState artist_followed = 0xa;
|
||||
optional GenericPresenceState generic = 0xb;
|
||||
}
|
||||
|
||||
message StateList {
|
||||
repeated State states = 0x1;
|
||||
}
|
||||
|
||||
8
src/inputs/librespot-c/src/proto/pubsub.proto
Normal file
8
src/inputs/librespot-c/src/proto/pubsub.proto
Normal file
@@ -0,0 +1,8 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message Subscription {
|
||||
optional string uri = 0x1;
|
||||
optional int32 expiry = 0x2;
|
||||
optional int32 status_code = 0x3;
|
||||
}
|
||||
|
||||
58
src/inputs/librespot-c/src/proto/radio.proto
Normal file
58
src/inputs/librespot-c/src/proto/radio.proto
Normal file
@@ -0,0 +1,58 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message RadioRequest {
|
||||
repeated string uris = 0x1;
|
||||
optional int32 salt = 0x2;
|
||||
optional int32 length = 0x4;
|
||||
optional string stationId = 0x5;
|
||||
repeated string lastTracks = 0x6;
|
||||
}
|
||||
|
||||
message MultiSeedRequest {
|
||||
repeated string uris = 0x1;
|
||||
}
|
||||
|
||||
message Feedback {
|
||||
optional string uri = 0x1;
|
||||
optional string type = 0x2;
|
||||
optional double timestamp = 0x3;
|
||||
}
|
||||
|
||||
message Tracks {
|
||||
repeated string gids = 0x1;
|
||||
optional string source = 0x2;
|
||||
optional string identity = 0x3;
|
||||
repeated string tokens = 0x4;
|
||||
repeated Feedback feedback = 0x5;
|
||||
}
|
||||
|
||||
message Station {
|
||||
optional string id = 0x1;
|
||||
optional string title = 0x2;
|
||||
optional string titleUri = 0x3;
|
||||
optional string subtitle = 0x4;
|
||||
optional string subtitleUri = 0x5;
|
||||
optional string imageUri = 0x6;
|
||||
optional double lastListen = 0x7;
|
||||
repeated string seeds = 0x8;
|
||||
optional int32 thumbsUp = 0x9;
|
||||
optional int32 thumbsDown = 0xa;
|
||||
}
|
||||
|
||||
message Rules {
|
||||
optional string js = 0x1;
|
||||
}
|
||||
|
||||
message StationResponse {
|
||||
optional Station station = 0x1;
|
||||
repeated Feedback feedback = 0x2;
|
||||
}
|
||||
|
||||
message StationList {
|
||||
repeated Station stations = 0x1;
|
||||
}
|
||||
|
||||
message LikedPlaylist {
|
||||
optional string uri = 0x1;
|
||||
}
|
||||
|
||||
44
src/inputs/librespot-c/src/proto/search.proto
Normal file
44
src/inputs/librespot-c/src/proto/search.proto
Normal file
@@ -0,0 +1,44 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message SearchRequest {
|
||||
optional string query = 0x1;
|
||||
optional Type type = 0x2;
|
||||
enum Type {
|
||||
TRACK = 0x0;
|
||||
ALBUM = 0x1;
|
||||
ARTIST = 0x2;
|
||||
PLAYLIST = 0x3;
|
||||
USER = 0x4;
|
||||
}
|
||||
optional int32 limit = 0x3;
|
||||
optional int32 offset = 0x4;
|
||||
optional bool did_you_mean = 0x5;
|
||||
optional string spotify_uri = 0x2;
|
||||
repeated bytes file_id = 0x3;
|
||||
optional string url = 0x4;
|
||||
optional string slask_id = 0x5;
|
||||
}
|
||||
|
||||
message Playlist {
|
||||
optional string uri = 0x1;
|
||||
optional string name = 0x2;
|
||||
repeated Image image = 0x3;
|
||||
}
|
||||
|
||||
message User {
|
||||
optional string username = 0x1;
|
||||
optional string full_name = 0x2;
|
||||
repeated Image image = 0x3;
|
||||
optional sint32 followers = 0x4;
|
||||
}
|
||||
|
||||
message SearchReply {
|
||||
optional sint32 hits = 0x1;
|
||||
repeated Track track = 0x2;
|
||||
repeated Album album = 0x3;
|
||||
repeated Artist artist = 0x4;
|
||||
repeated Playlist playlist = 0x5;
|
||||
optional string did_you_mean = 0x6;
|
||||
repeated User user = 0x7;
|
||||
}
|
||||
|
||||
12
src/inputs/librespot-c/src/proto/social.proto
Normal file
12
src/inputs/librespot-c/src/proto/social.proto
Normal file
@@ -0,0 +1,12 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message DecorationData {
|
||||
optional string username = 0x1;
|
||||
optional string full_name = 0x2;
|
||||
optional string image_url = 0x3;
|
||||
optional string large_image_url = 0x5;
|
||||
optional string first_name = 0x6;
|
||||
optional string last_name = 0x7;
|
||||
optional string facebook_uid = 0x8;
|
||||
}
|
||||
|
||||
49
src/inputs/librespot-c/src/proto/socialgraph.proto
Normal file
49
src/inputs/librespot-c/src/proto/socialgraph.proto
Normal file
@@ -0,0 +1,49 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message CountReply {
|
||||
repeated int32 counts = 0x1;
|
||||
}
|
||||
|
||||
message UserListRequest {
|
||||
optional string last_result = 0x1;
|
||||
optional int32 count = 0x2;
|
||||
optional bool include_length = 0x3;
|
||||
}
|
||||
|
||||
message UserListReply {
|
||||
repeated User users = 0x1;
|
||||
optional int32 length = 0x2;
|
||||
}
|
||||
|
||||
message User {
|
||||
optional string username = 0x1;
|
||||
optional int32 subscriber_count = 0x2;
|
||||
optional int32 subscription_count = 0x3;
|
||||
}
|
||||
|
||||
message ArtistListReply {
|
||||
repeated Artist artists = 0x1;
|
||||
}
|
||||
|
||||
message Artist {
|
||||
optional string artistid = 0x1;
|
||||
optional int32 subscriber_count = 0x2;
|
||||
}
|
||||
|
||||
message StringListRequest {
|
||||
repeated string args = 0x1;
|
||||
}
|
||||
|
||||
message StringListReply {
|
||||
repeated string reply = 0x1;
|
||||
}
|
||||
|
||||
message TopPlaylistsRequest {
|
||||
optional string username = 0x1;
|
||||
optional int32 count = 0x2;
|
||||
}
|
||||
|
||||
message TopPlaylistsReply {
|
||||
repeated string uris = 0x1;
|
||||
}
|
||||
|
||||
133
src/inputs/librespot-c/src/proto/spirc.proto
Normal file
133
src/inputs/librespot-c/src/proto/spirc.proto
Normal file
@@ -0,0 +1,133 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message Frame {
|
||||
optional uint32 version = 0x1;
|
||||
optional string ident = 0x2;
|
||||
optional string protocol_version = 0x3;
|
||||
optional uint32 seq_nr = 0x4;
|
||||
optional MessageType typ = 0x5;
|
||||
optional DeviceState device_state = 0x7;
|
||||
optional Goodbye goodbye = 0xb;
|
||||
optional State state = 0xc;
|
||||
optional uint32 position = 0xd;
|
||||
optional uint32 volume = 0xe;
|
||||
optional int64 state_update_id = 0x11;
|
||||
repeated string recipient = 0x12;
|
||||
optional bytes context_player_state = 0x13;
|
||||
optional string new_name = 0x14;
|
||||
optional Metadata metadata = 0x19;
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
kMessageTypeHello = 0x1;
|
||||
kMessageTypeGoodbye = 0x2;
|
||||
kMessageTypeProbe = 0x3;
|
||||
kMessageTypeNotify = 0xa;
|
||||
kMessageTypeLoad = 0x14;
|
||||
kMessageTypePlay = 0x15;
|
||||
kMessageTypePause = 0x16;
|
||||
kMessageTypePlayPause = 0x17;
|
||||
kMessageTypeSeek = 0x18;
|
||||
kMessageTypePrev = 0x19;
|
||||
kMessageTypeNext = 0x1a;
|
||||
kMessageTypeVolume = 0x1b;
|
||||
kMessageTypeShuffle = 0x1c;
|
||||
kMessageTypeRepeat = 0x1d;
|
||||
kMessageTypeVolumeDown = 0x1f;
|
||||
kMessageTypeVolumeUp = 0x20;
|
||||
kMessageTypeReplace = 0x21;
|
||||
kMessageTypeLogout = 0x22;
|
||||
kMessageTypeAction = 0x23;
|
||||
kMessageTypeRename = 0x24;
|
||||
kMessageTypeUpdateMetadata = 0x80;
|
||||
}
|
||||
|
||||
message DeviceState {
|
||||
optional string sw_version = 0x1;
|
||||
optional bool is_active = 0xa;
|
||||
optional bool can_play = 0xb;
|
||||
optional uint32 volume = 0xc;
|
||||
optional string name = 0xd;
|
||||
optional uint32 error_code = 0xe;
|
||||
optional int64 became_active_at = 0xf;
|
||||
optional string error_message = 0x10;
|
||||
repeated Capability capabilities = 0x11;
|
||||
optional string context_player_error = 0x14;
|
||||
repeated Metadata metadata = 0x19;
|
||||
}
|
||||
|
||||
message Capability {
|
||||
optional CapabilityType typ = 0x1;
|
||||
repeated int64 intValue = 0x2;
|
||||
repeated string stringValue = 0x3;
|
||||
}
|
||||
|
||||
enum CapabilityType {
|
||||
kSupportedContexts = 0x1;
|
||||
kCanBePlayer = 0x2;
|
||||
kRestrictToLocal = 0x3;
|
||||
kDeviceType = 0x4;
|
||||
kGaiaEqConnectId = 0x5;
|
||||
kSupportsLogout = 0x6;
|
||||
kIsObservable = 0x7;
|
||||
kVolumeSteps = 0x8;
|
||||
kSupportedTypes = 0x9;
|
||||
kCommandAcks = 0xa;
|
||||
kSupportsRename = 0xb;
|
||||
kHidden = 0xc;
|
||||
kSupportsPlaylistV2 = 0xd;
|
||||
kSupportsExternalEpisodes = 0xe;
|
||||
}
|
||||
|
||||
message Goodbye {
|
||||
optional string reason = 0x1;
|
||||
}
|
||||
|
||||
message State {
|
||||
optional string context_uri = 0x2;
|
||||
optional uint32 index = 0x3;
|
||||
optional uint32 position_ms = 0x4;
|
||||
optional PlayStatus status = 0x5;
|
||||
optional uint64 position_measured_at = 0x7;
|
||||
optional string context_description = 0x8;
|
||||
optional bool shuffle = 0xd;
|
||||
optional bool repeat = 0xe;
|
||||
optional string last_command_ident = 0x14;
|
||||
optional uint32 last_command_msgid = 0x15;
|
||||
optional bool playing_from_fallback = 0x18;
|
||||
optional uint32 row = 0x19;
|
||||
optional uint32 playing_track_index = 0x1a;
|
||||
repeated TrackRef track = 0x1b;
|
||||
optional Ad ad = 0x1c;
|
||||
}
|
||||
|
||||
enum PlayStatus {
|
||||
kPlayStatusStop = 0x0;
|
||||
kPlayStatusPlay = 0x1;
|
||||
kPlayStatusPause = 0x2;
|
||||
kPlayStatusLoading = 0x3;
|
||||
}
|
||||
|
||||
message TrackRef {
|
||||
optional bytes gid = 0x1;
|
||||
optional string uri = 0x2;
|
||||
optional bool queued = 0x3;
|
||||
optional string context = 0x4;
|
||||
}
|
||||
|
||||
message Ad {
|
||||
optional int32 next = 0x1;
|
||||
optional bytes ogg_fid = 0x2;
|
||||
optional bytes image_fid = 0x3;
|
||||
optional int32 duration = 0x4;
|
||||
optional string click_url = 0x5;
|
||||
optional string impression_url = 0x6;
|
||||
optional string product = 0x7;
|
||||
optional string advertiser = 0x8;
|
||||
optional bytes gid = 0x9;
|
||||
}
|
||||
|
||||
message Metadata {
|
||||
optional string type = 0x1;
|
||||
optional string metadata = 0x2;
|
||||
}
|
||||
43
src/inputs/librespot-c/src/proto/suggest.proto
Normal file
43
src/inputs/librespot-c/src/proto/suggest.proto
Normal file
@@ -0,0 +1,43 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message Track {
|
||||
optional bytes gid = 0x1;
|
||||
optional string name = 0x2;
|
||||
optional bytes image = 0x3;
|
||||
repeated string artist_name = 0x4;
|
||||
repeated bytes artist_gid = 0x5;
|
||||
optional uint32 rank = 0x6;
|
||||
}
|
||||
|
||||
message Artist {
|
||||
optional bytes gid = 0x1;
|
||||
optional string name = 0x2;
|
||||
optional bytes image = 0x3;
|
||||
optional uint32 rank = 0x6;
|
||||
}
|
||||
|
||||
message Album {
|
||||
optional bytes gid = 0x1;
|
||||
optional string name = 0x2;
|
||||
optional bytes image = 0x3;
|
||||
repeated string artist_name = 0x4;
|
||||
repeated bytes artist_gid = 0x5;
|
||||
optional uint32 rank = 0x6;
|
||||
}
|
||||
|
||||
message Playlist {
|
||||
optional string uri = 0x1;
|
||||
optional string name = 0x2;
|
||||
optional string image_uri = 0x3;
|
||||
optional string owner_name = 0x4;
|
||||
optional string owner_uri = 0x5;
|
||||
optional uint32 rank = 0x6;
|
||||
}
|
||||
|
||||
message Suggestions {
|
||||
repeated Track track = 0x1;
|
||||
repeated Album album = 0x2;
|
||||
repeated Artist artist = 0x3;
|
||||
repeated Playlist playlist = 0x4;
|
||||
}
|
||||
|
||||
6
src/inputs/librespot-c/src/proto/toplist.proto
Normal file
6
src/inputs/librespot-c/src/proto/toplist.proto
Normal file
@@ -0,0 +1,6 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message Toplist {
|
||||
repeated string items = 0x1;
|
||||
}
|
||||
|
||||
52
src/inputs/librespot-c/src/shannon/Shannon.h
Normal file
52
src/inputs/librespot-c/src/shannon/Shannon.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* $Id: $ */
|
||||
/* Shannon: Shannon stream cipher and MAC header files */
|
||||
|
||||
/*
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND AGAINST
|
||||
INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _SHN_DEFINED
|
||||
#define _SHN_DEFINED 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SHANNON_N 16
|
||||
|
||||
typedef struct {
|
||||
uint32_t R[SHANNON_N]; /* Working storage for the shift register */
|
||||
uint32_t CRC[SHANNON_N]; /* Working storage for CRC accumulation */
|
||||
uint32_t initR[SHANNON_N]; /* saved register contents */
|
||||
uint32_t konst; /* key dependent semi-constant */
|
||||
uint32_t sbuf; /* encryption buffer */
|
||||
uint32_t mbuf; /* partial word MAC buffer */
|
||||
int nbuf; /* number of part-word stream bits buffered */
|
||||
} shn_ctx;
|
||||
|
||||
/* interface definitions */
|
||||
void shn_key(shn_ctx *c, const uint8_t key[], int keylen); /* set key */
|
||||
void shn_nonce(shn_ctx *c, const uint8_t nonce[], int nlen); /* set Init Vector */
|
||||
void shn_stream(shn_ctx *c, uint8_t *buf, int nbytes); /* stream cipher */
|
||||
void shn_maconly(shn_ctx *c, uint8_t *buf, int nbytes); /* accumulate MAC */
|
||||
void shn_encrypt(shn_ctx *c, uint8_t *buf, int nbytes); /* encrypt + MAC */
|
||||
void shn_decrypt(shn_ctx *c, uint8_t *buf, int nbytes); /* decrypt + MAC */
|
||||
void shn_finish(shn_ctx *c, uint8_t *buf, int nbytes); /* finalise MAC */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _SHN_DEFINED */
|
||||
626
src/inputs/librespot-c/src/shannon/ShannonFast.c
Normal file
626
src/inputs/librespot-c/src/shannon/ShannonFast.c
Normal file
@@ -0,0 +1,626 @@
|
||||
/* $Id: shnfast.c 442 2006-05-12 23:22:21Z ggr $ */
|
||||
/* ShannonFast: Shannon stream cipher and MAC -- fast implementation */
|
||||
|
||||
/*
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND AGAINST
|
||||
INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* interface, multiplication table and SBox */
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "ShannonInternal.h"
|
||||
|
||||
/*
|
||||
* FOLD is how many register cycles need to be performed after combining the
|
||||
* last byte of key and non-linear feedback, before every byte depends on every
|
||||
* byte of the key. This depends on the feedback and nonlinear functions, and
|
||||
* on where they are combined into the register. Making it same as the
|
||||
* register length is a safe and conservative choice.
|
||||
*/
|
||||
#define FOLD N /* how many iterations of folding to do */
|
||||
#define INITKONST 0x6996c53a /* value of KONST to use during key loading */
|
||||
#define KEYP 13 /* where to insert key/MAC words */
|
||||
|
||||
#define Byte(x,i) ((UCHAR)(((x) >> (8*i)) & 0xFF))
|
||||
|
||||
/* define IS_LITTLE_ENDIAN for faster operation when appropriate */
|
||||
#ifdef IS_LITTLE_ENDIAN
|
||||
/* Useful macros -- little endian words on a little endian machine */
|
||||
#define BYTE2WORD(b) (*(WORD *)(b))
|
||||
#define WORD2BYTE(w, b) ((*(WORD *)(b)) = w)
|
||||
#define XORWORD(w, b) ((*(WORD *)(b)) ^= w)
|
||||
#else
|
||||
/* Useful macros -- machine independent little-endian version */
|
||||
#define BYTE2WORD(b) ( \
|
||||
(((WORD)(b)[3] & 0xFF)<<24) | \
|
||||
(((WORD)(b)[2] & 0xFF)<<16) | \
|
||||
(((WORD)(b)[1] & 0xFF)<<8) | \
|
||||
(((WORD)(b)[0] & 0xFF)) \
|
||||
)
|
||||
#define WORD2BYTE(w, b) { \
|
||||
(b)[3] = Byte(w,3); \
|
||||
(b)[2] = Byte(w,2); \
|
||||
(b)[1] = Byte(w,1); \
|
||||
(b)[0] = Byte(w,0); \
|
||||
}
|
||||
#define XORWORD(w, b) { \
|
||||
(b)[3] ^= Byte(w,3); \
|
||||
(b)[2] ^= Byte(w,2); \
|
||||
(b)[1] ^= Byte(w,1); \
|
||||
(b)[0] ^= Byte(w,0); \
|
||||
}
|
||||
#endif
|
||||
|
||||
/* give correct offset for the current position of the register,
|
||||
* where logically R[0] is at position "zero". Note that this works for
|
||||
* both the stream register and the CRC register.
|
||||
*/
|
||||
#define OFF(zero, i) (((zero)+(i)) % N)
|
||||
|
||||
/* step the shift register */
|
||||
/* After stepping, "zero" moves right one place */
|
||||
#define STEP(c,z) \
|
||||
{ \
|
||||
t = c->R[OFF(z,12)] ^ c->R[OFF(z,13)] ^ c->konst; \
|
||||
/* Sbox 1 */ \
|
||||
t ^= ROTL(t, 5) | ROTL(t, 7); \
|
||||
t ^= ROTL(t, 19) | ROTL(t, 22); \
|
||||
c->R[OFF(z,0)] = t ^ ROTL(c->R[OFF(z,0)],1); \
|
||||
t = c->R[OFF((z+1),2)] ^ c->R[OFF((z+1),15)]; \
|
||||
/* Sbox 2 */ \
|
||||
t ^= ROTL(t, 7) | ROTL(t, 22); \
|
||||
t ^= ROTL(t, 5) | ROTL(t, 19); \
|
||||
c->R[OFF((z+1),0)] ^= t; \
|
||||
c->sbuf = t ^ c->R[OFF((z+1),8)] ^ c->R[OFF((z+1),12)]; \
|
||||
}
|
||||
|
||||
static void
|
||||
cycle(shn_ctx *c)
|
||||
{
|
||||
WORD t;
|
||||
int i;
|
||||
|
||||
/* nonlinear feedback function */
|
||||
STEP(c,0);
|
||||
/* shift register */
|
||||
t = c->R[0];
|
||||
for (i = 1; i < N; ++i)
|
||||
c->R[i-1] = c->R[i];
|
||||
c->R[N-1] = t;
|
||||
}
|
||||
|
||||
/* The Shannon MAC function is modelled after the concepts of Phelix and SHA.
|
||||
* Basically, words to be accumulated in the MAC are incorporated in two
|
||||
* different ways:
|
||||
* 1. They are incorporated into the stream cipher register at a place
|
||||
* where they will immediately have a nonlinear effect on the state
|
||||
* 2. They are incorporated into bit-parallel CRC-16 registers; the
|
||||
* contents of these registers will be used in MAC finalization.
|
||||
*/
|
||||
|
||||
|
||||
/* Accumulate a CRC of input words, later to be fed into MAC.
|
||||
* This is actually 32 parallel CRC-16s, using the IBM CRC-16
|
||||
* polynomial x^16 + x^15 + x^2 + 1.
|
||||
*/
|
||||
#define CRCFUNC(c,i,z) \
|
||||
{ \
|
||||
c->CRC[OFF(z,0)] ^= c->CRC[OFF(z,2)] ^ c->CRC[OFF(z,15)] ^ i; \
|
||||
}
|
||||
|
||||
static void
|
||||
crcfunc(shn_ctx *c, WORD i)
|
||||
{
|
||||
WORD t;
|
||||
|
||||
CRCFUNC(c, i, 0);
|
||||
/* now correct alignment of CRC accumulator */
|
||||
t = c->CRC[0];
|
||||
for (i = 1; i < N; ++i)
|
||||
c->CRC[i-1] = c->CRC[i];
|
||||
c->CRC[N-1] = t;
|
||||
}
|
||||
|
||||
/* Normal MAC word processing: do both SHA and CRC.
|
||||
*/
|
||||
static void
|
||||
macfunc(shn_ctx *c, WORD i)
|
||||
{
|
||||
crcfunc(c, i);
|
||||
c->R[KEYP] ^= i;
|
||||
}
|
||||
|
||||
/* initialise to known state
|
||||
*/
|
||||
static void
|
||||
shn_initstate(shn_ctx *c)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Register initialised to Fibonacci numbers; Counter zeroed. */
|
||||
c->R[0] = 1;
|
||||
c->R[1] = 1;
|
||||
for (i = 2; i < N; ++i)
|
||||
c->R[i] = c->R[i-1] + c->R[i-2];
|
||||
c->konst = INITKONST;
|
||||
}
|
||||
|
||||
/* Save the current register state
|
||||
*/
|
||||
static void
|
||||
shn_savestate(shn_ctx *c)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < N; ++i)
|
||||
c->initR[i] = c->R[i];
|
||||
}
|
||||
|
||||
/* initialise to previously saved register state
|
||||
*/
|
||||
static void
|
||||
shn_reloadstate(shn_ctx *c)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < N; ++i)
|
||||
c->R[i] = c->initR[i];
|
||||
}
|
||||
|
||||
/* Initialise "konst"
|
||||
*/
|
||||
static void
|
||||
shn_genkonst(shn_ctx *c)
|
||||
{
|
||||
c->konst = c->R[0];
|
||||
}
|
||||
|
||||
/* Load key material into the register
|
||||
*/
|
||||
#define ADDKEY(k) \
|
||||
c->R[KEYP] ^= (k);
|
||||
|
||||
/* nonlinear diffusion of register for key and MAC */
|
||||
#define DROUND(z) { register WORD t; STEP(c,z); }
|
||||
static void
|
||||
shn_diffuse(shn_ctx *c)
|
||||
{
|
||||
/* relies on FOLD == N! */
|
||||
DROUND(0);
|
||||
DROUND(1);
|
||||
DROUND(2);
|
||||
DROUND(3);
|
||||
DROUND(4);
|
||||
DROUND(5);
|
||||
DROUND(6);
|
||||
DROUND(7);
|
||||
DROUND(8);
|
||||
DROUND(9);
|
||||
DROUND(10);
|
||||
DROUND(11);
|
||||
DROUND(12);
|
||||
DROUND(13);
|
||||
DROUND(14);
|
||||
DROUND(15);
|
||||
}
|
||||
|
||||
/* common actions for loading key material
|
||||
* Allow non-word-multiple key and nonce materianl
|
||||
* Note also initializes the CRC register as a side effect.
|
||||
*/
|
||||
static void
|
||||
shn_loadkey(shn_ctx *c, const uint8_t key[], int keylen)
|
||||
{
|
||||
int i, j;
|
||||
WORD k;
|
||||
uint8_t xtra[4];
|
||||
|
||||
/* start folding in key */
|
||||
for (i = 0; i < (keylen & ~0x3); i += 4)
|
||||
{
|
||||
k = BYTE2WORD(&key[i]);
|
||||
ADDKEY(k);
|
||||
cycle(c);
|
||||
}
|
||||
|
||||
/* if there were any extra key bytes, zero pad to a word */
|
||||
if (i < keylen) {
|
||||
for (j = 0 /* i unchanged */; i < keylen; ++i)
|
||||
xtra[j++] = key[i];
|
||||
for (/* j unchanged */; j < 4; ++j)
|
||||
xtra[j] = 0;
|
||||
k = BYTE2WORD(xtra);
|
||||
ADDKEY(k);
|
||||
cycle(c);
|
||||
}
|
||||
|
||||
/* also fold in the length of the key */
|
||||
ADDKEY(keylen);
|
||||
cycle(c);
|
||||
|
||||
/* save a copy of the register */
|
||||
for (i = 0; i < N; ++i)
|
||||
c->CRC[i] = c->R[i];
|
||||
|
||||
/* now diffuse */
|
||||
shn_diffuse(c);
|
||||
|
||||
/* now xor the copy back -- makes key loading irreversible */
|
||||
for (i = 0; i < N; ++i)
|
||||
c->R[i] ^= c->CRC[i];
|
||||
}
|
||||
|
||||
/* Published "key" interface
|
||||
*/
|
||||
void
|
||||
shn_key(shn_ctx *c, const uint8_t key[], int keylen)
|
||||
{
|
||||
shn_initstate(c);
|
||||
shn_loadkey(c, key, keylen);
|
||||
shn_genkonst(c);
|
||||
shn_savestate(c);
|
||||
c->nbuf = 0;
|
||||
}
|
||||
|
||||
/* Published "nonce" interface
|
||||
*/
|
||||
void
|
||||
shn_nonce(shn_ctx *c, const uint8_t nonce[], int noncelen)
|
||||
{
|
||||
shn_reloadstate(c);
|
||||
c->konst = INITKONST;
|
||||
shn_loadkey(c, nonce, noncelen);
|
||||
shn_genkonst(c);
|
||||
c->nbuf = 0;
|
||||
}
|
||||
|
||||
/* XOR pseudo-random bytes into buffer
|
||||
* Note: doesn't play well with MAC functions.
|
||||
*/
|
||||
#define SROUND(z) \
|
||||
{ register WORD t; \
|
||||
STEP(c,z); \
|
||||
XORWORD(c->sbuf, buf+(z*4)); \
|
||||
}
|
||||
void
|
||||
shn_stream(shn_ctx *c, uint8_t *buf, int nbytes)
|
||||
{
|
||||
/* handle any previously buffered bytes */
|
||||
while (c->nbuf != 0 && nbytes != 0) {
|
||||
*buf++ ^= c->sbuf & 0xFF;
|
||||
c->sbuf >>= 8;
|
||||
c->nbuf -= 8;
|
||||
--nbytes;
|
||||
}
|
||||
|
||||
/* do lots at a time, if there's enough to do */
|
||||
while (nbytes >= N*4)
|
||||
{
|
||||
SROUND(0);
|
||||
SROUND(1);
|
||||
SROUND(2);
|
||||
SROUND(3);
|
||||
SROUND(4);
|
||||
SROUND(5);
|
||||
SROUND(6);
|
||||
SROUND(7);
|
||||
SROUND(8);
|
||||
SROUND(9);
|
||||
SROUND(10);
|
||||
SROUND(11);
|
||||
SROUND(12);
|
||||
SROUND(13);
|
||||
SROUND(14);
|
||||
SROUND(15);
|
||||
buf += 4*N;
|
||||
nbytes -= N*4;
|
||||
}
|
||||
|
||||
/* do small or odd size buffers the slow way */
|
||||
while (4 <= nbytes) {
|
||||
cycle(c);
|
||||
XORWORD(c->sbuf, buf);
|
||||
buf += 4;
|
||||
nbytes -= 4;
|
||||
}
|
||||
|
||||
/* handle any trailing bytes */
|
||||
if (nbytes != 0) {
|
||||
cycle(c);
|
||||
c->nbuf = 32;
|
||||
while (c->nbuf != 0 && nbytes != 0) {
|
||||
*buf++ ^= c->sbuf & 0xFF;
|
||||
c->sbuf >>= 8;
|
||||
c->nbuf -= 8;
|
||||
--nbytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* accumulate words into MAC without encryption
|
||||
* Note that plaintext is accumulated for MAC.
|
||||
*/
|
||||
#define MROUND(z) \
|
||||
{ register WORD t, t1; \
|
||||
t1 = BYTE2WORD(buf+(z*4)); \
|
||||
STEP(c,z); \
|
||||
CRCFUNC(c,t1,z); \
|
||||
c->R[OFF(z+1,KEYP)] ^= t1; \
|
||||
}
|
||||
void
|
||||
shn_maconly(shn_ctx *c, uint8_t *buf, int nbytes)
|
||||
{
|
||||
/* handle any previously buffered bytes */
|
||||
if (c->nbuf != 0) {
|
||||
while (c->nbuf != 0 && nbytes != 0) {
|
||||
c->mbuf ^= (*buf++) << (32 - c->nbuf);
|
||||
c->nbuf -= 8;
|
||||
--nbytes;
|
||||
}
|
||||
if (c->nbuf != 0) /* not a whole word yet */
|
||||
return;
|
||||
/* LFSR already cycled */
|
||||
macfunc(c, c->mbuf);
|
||||
}
|
||||
|
||||
/* do lots at a time, if there's enough to do */
|
||||
while (4*N <= nbytes)
|
||||
{
|
||||
MROUND( 0);
|
||||
MROUND( 1);
|
||||
MROUND( 2);
|
||||
MROUND( 3);
|
||||
MROUND( 4);
|
||||
MROUND( 5);
|
||||
MROUND( 6);
|
||||
MROUND( 7);
|
||||
MROUND( 8);
|
||||
MROUND( 9);
|
||||
MROUND(10);
|
||||
MROUND(11);
|
||||
MROUND(12);
|
||||
MROUND(13);
|
||||
MROUND(14);
|
||||
MROUND(15);
|
||||
buf += 4*N;
|
||||
nbytes -= 4*N;
|
||||
}
|
||||
|
||||
/* do small or odd size buffers the slow way */
|
||||
while (4 <= nbytes) {
|
||||
cycle(c);
|
||||
macfunc(c, BYTE2WORD(buf));
|
||||
buf += 4;
|
||||
nbytes -= 4;
|
||||
}
|
||||
|
||||
/* handle any trailing bytes */
|
||||
if (nbytes != 0) {
|
||||
cycle(c);
|
||||
c->mbuf = 0;
|
||||
c->nbuf = 32;
|
||||
while (nbytes != 0) {
|
||||
c->mbuf ^= (*buf++) << (32 - c->nbuf);
|
||||
c->nbuf -= 8;
|
||||
--nbytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Combined MAC and encryption.
|
||||
* Note that plaintext is accumulated for MAC.
|
||||
*/
|
||||
#define EROUND(z) \
|
||||
{ register WORD t, t3; \
|
||||
STEP(c,z); \
|
||||
t3 = BYTE2WORD(buf+(z*4)); \
|
||||
CRCFUNC(c,t3,z); \
|
||||
c->R[OFF((z+1),KEYP)] ^= t3; \
|
||||
t3 ^= c->sbuf; \
|
||||
WORD2BYTE(t3,buf+(z*4)); \
|
||||
}
|
||||
void
|
||||
shn_encrypt(shn_ctx *c, uint8_t *buf, int nbytes)
|
||||
{
|
||||
WORD t3 = 0;
|
||||
|
||||
/* handle any previously buffered bytes */
|
||||
if (c->nbuf != 0) {
|
||||
while (c->nbuf != 0 && nbytes != 0) {
|
||||
c->mbuf ^= *buf << (32 - c->nbuf);
|
||||
*buf ^= (c->sbuf >> (32 - c->nbuf)) & 0xFF;
|
||||
++buf;
|
||||
c->nbuf -= 8;
|
||||
--nbytes;
|
||||
}
|
||||
if (c->nbuf != 0) /* not a whole word yet */
|
||||
return;
|
||||
/* LFSR already cycled */
|
||||
macfunc(c, c->mbuf);
|
||||
}
|
||||
|
||||
/* do lots at a time, if there's enough to do */
|
||||
while (4*N <= nbytes)
|
||||
{
|
||||
EROUND( 0);
|
||||
EROUND( 1);
|
||||
EROUND( 2);
|
||||
EROUND( 3);
|
||||
EROUND( 4);
|
||||
EROUND( 5);
|
||||
EROUND( 6);
|
||||
EROUND( 7);
|
||||
EROUND( 8);
|
||||
EROUND( 9);
|
||||
EROUND(10);
|
||||
EROUND(11);
|
||||
EROUND(12);
|
||||
EROUND(13);
|
||||
EROUND(14);
|
||||
EROUND(15);
|
||||
buf += 4*N;
|
||||
nbytes -= 4*N;
|
||||
}
|
||||
|
||||
/* do small or odd size buffers the slow way */
|
||||
while (4 <= nbytes) {
|
||||
cycle(c);
|
||||
t3 = BYTE2WORD(buf);
|
||||
macfunc(c, t3);
|
||||
t3 ^= c->sbuf;
|
||||
WORD2BYTE(t3, buf);
|
||||
nbytes -= 4;
|
||||
buf += 4;
|
||||
}
|
||||
|
||||
/* handle any trailing bytes */
|
||||
if (nbytes != 0) {
|
||||
cycle(c);
|
||||
c->mbuf = 0;
|
||||
c->nbuf = 32;
|
||||
while (c->nbuf != 0 && nbytes != 0) {
|
||||
c->mbuf ^= *buf << (32 - c->nbuf);
|
||||
*buf ^= (c->sbuf >> (32 - c->nbuf)) & 0xFF;
|
||||
++buf;
|
||||
c->nbuf -= 8;
|
||||
--nbytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Combined MAC and decryption.
|
||||
* Note that plaintext is accumulated for MAC.
|
||||
*/
|
||||
#undef DROUND
|
||||
#define DROUND(z) \
|
||||
{ register WORD t, t3; \
|
||||
STEP(c,z); \
|
||||
t3 = BYTE2WORD(buf+(z*4)); \
|
||||
t3 ^= c->sbuf; \
|
||||
CRCFUNC(c,t3,z); \
|
||||
c->R[OFF((z+1),KEYP)] ^= t3; \
|
||||
WORD2BYTE(t3, buf+(z*4)); \
|
||||
}
|
||||
void
|
||||
shn_decrypt(shn_ctx *c, uint8_t *buf, int nbytes)
|
||||
{
|
||||
WORD t3 = 0;
|
||||
|
||||
/* handle any previously buffered bytes */
|
||||
if (c->nbuf != 0) {
|
||||
while (c->nbuf != 0 && nbytes != 0) {
|
||||
*buf ^= (c->sbuf >> (32 - c->nbuf)) & 0xFF;
|
||||
c->mbuf ^= *buf << (32 - c->nbuf);
|
||||
++buf;
|
||||
c->nbuf -= 8;
|
||||
--nbytes;
|
||||
}
|
||||
if (c->nbuf != 0) /* not a whole word yet */
|
||||
return;
|
||||
/* LFSR already cycled */
|
||||
macfunc(c, c->mbuf);
|
||||
}
|
||||
|
||||
/* now do lots at a time, if there's enough */
|
||||
while (4*N <= nbytes)
|
||||
{
|
||||
DROUND( 0);
|
||||
DROUND( 1);
|
||||
DROUND( 2);
|
||||
DROUND( 3);
|
||||
DROUND( 4);
|
||||
DROUND( 5);
|
||||
DROUND( 6);
|
||||
DROUND( 7);
|
||||
DROUND( 8);
|
||||
DROUND( 9);
|
||||
DROUND(10);
|
||||
DROUND(11);
|
||||
DROUND(12);
|
||||
DROUND(13);
|
||||
DROUND(14);
|
||||
DROUND(15);
|
||||
buf += 4*N;
|
||||
nbytes -= 4*N;
|
||||
}
|
||||
|
||||
/* do small or odd size buffers the slow way */
|
||||
while (4 <= nbytes) {
|
||||
cycle(c);
|
||||
t3 = BYTE2WORD(buf);
|
||||
t3 ^= c->sbuf;
|
||||
macfunc(c, t3);
|
||||
WORD2BYTE(t3, buf);
|
||||
nbytes -= 4;
|
||||
buf += 4;
|
||||
}
|
||||
|
||||
/* handle any trailing bytes */
|
||||
if (nbytes != 0) {
|
||||
cycle(c);
|
||||
c->mbuf = 0;
|
||||
c->nbuf = 32;
|
||||
while (c->nbuf != 0 && nbytes != 0) {
|
||||
*buf ^= (c->sbuf >> (32 - c->nbuf)) & 0xFF;
|
||||
c->mbuf ^= *buf << (32 - c->nbuf);
|
||||
++buf;
|
||||
c->nbuf -= 8;
|
||||
--nbytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Having accumulated a MAC, finish processing and return it.
|
||||
* Note that any unprocessed bytes are treated as if
|
||||
* they were encrypted zero bytes, so plaintext (zero) is accumulated.
|
||||
*/
|
||||
void
|
||||
shn_finish(shn_ctx *c, uint8_t *buf, int nbytes)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* handle any previously buffered bytes */
|
||||
if (c->nbuf != 0) {
|
||||
/* LFSR already cycled */
|
||||
macfunc(c, c->mbuf);
|
||||
}
|
||||
|
||||
/* perturb the MAC to mark end of input.
|
||||
* Note that only the stream register is updated, not the CRC. This is an
|
||||
* action that can't be duplicated by passing in plaintext, hence
|
||||
* defeating any kind of extension attack.
|
||||
*/
|
||||
cycle(c);
|
||||
ADDKEY(INITKONST ^ (c->nbuf << 3));
|
||||
c->nbuf = 0;
|
||||
|
||||
/* now add the CRC to the stream register and diffuse it */
|
||||
for (i = 0; i < N; ++i)
|
||||
c->R[i] ^= c->CRC[i];
|
||||
shn_diffuse(c);
|
||||
|
||||
/* produce output from the stream buffer */
|
||||
while (nbytes > 0) {
|
||||
cycle(c);
|
||||
if (nbytes >= 4) {
|
||||
WORD2BYTE(c->sbuf, buf);
|
||||
nbytes -= 4;
|
||||
buf += 4;
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < nbytes; ++i)
|
||||
buf[i] = Byte(c->sbuf, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/inputs/librespot-c/src/shannon/ShannonInternal.h
Normal file
23
src/inputs/librespot-c/src/shannon/ShannonInternal.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef SHANNONINTERNAL_H
|
||||
#define SHANNONINTERNAL_H
|
||||
|
||||
#include "Shannon.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#define N SHANNON_N
|
||||
#define WORDSIZE 32
|
||||
#define UCHAR unsigned char
|
||||
|
||||
#define WORD uint32_t
|
||||
#define WORD_MAX UINT32_MAX
|
||||
|
||||
#if WORD_MAX == 0xffffffff
|
||||
#define ROTL(w,x) (((w) << (x))|((w) >> (32 - (x))))
|
||||
#define ROTR(w,x) (((w) >> (x))|((w) << (32 - (x))))
|
||||
#else
|
||||
#define ROTL(w,x) (((w) << (x))|(((w) & 0xffffffff) >> (32 - (x))))
|
||||
#define ROTR(w,x) ((((w) & 0xffffffff) >> (x))|((w) << (32 - (x))))
|
||||
#endif
|
||||
|
||||
#endif
|
||||
1
src/inputs/librespot-c/tests/.gitignore
vendored
Normal file
1
src/inputs/librespot-c/tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
test1
|
||||
10
src/inputs/librespot-c/tests/Makefile.am
Normal file
10
src/inputs/librespot-c/tests/Makefile.am
Normal file
@@ -0,0 +1,10 @@
|
||||
TEST_CFLAGS = $(CFLAGS) $(JSON_C_CFLAGS) $(LIBCURL_CFLAGS) $(LIBEVENT_CFLAGS) $(LIBGCRYPT_CFLAGS) $(LIBPROTOBUF_C_CFLAGS)
|
||||
TEST_LIBS = $(LIBS) $(JSON_C_LIBS) $(LIBCURL_LIBS) $(LIBEVENT_LIBS) $(LIBGCRYPT_LIBS) $(LIBPROTOBUF_C_LIBS)
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)
|
||||
|
||||
test1_SOURCES = test1.c
|
||||
test1_LDADD = $(top_builddir)/librespot-c.a -lpthread $(TEST_LIBS)
|
||||
test1_CFLAGS = $(TEST_CFLAGS)
|
||||
|
||||
check_PROGRAMS = test1
|
||||
357
src/inputs/librespot-c/tests/test1.c
Normal file
357
src/inputs/librespot-c/tests/test1.c
Normal file
@@ -0,0 +1,357 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// For file output
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "librespot-c.h"
|
||||
|
||||
static int audio_fd = -1;
|
||||
static int test_file = -1;
|
||||
static struct event_base *evbase;
|
||||
static struct evbuffer *audio_buf;
|
||||
|
||||
static int total_bytes;
|
||||
|
||||
#include <ctype.h> // for isprint()
|
||||
|
||||
static void
|
||||
hexdump_dummy(const char *msg, uint8_t *mem, size_t len)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
hexdump(const char *msg, uint8_t *mem, size_t len)
|
||||
{
|
||||
int i, j;
|
||||
int hexdump_cols = 16;
|
||||
|
||||
if (msg)
|
||||
printf("%s", msg);
|
||||
|
||||
for (i = 0; i < len + ((len % hexdump_cols) ? (hexdump_cols - len % hexdump_cols) : 0); i++)
|
||||
{
|
||||
if(i % hexdump_cols == 0)
|
||||
printf("0x%06x: ", i);
|
||||
|
||||
if (i < len)
|
||||
printf("%02x ", 0xFF & ((char*)mem)[i]);
|
||||
else
|
||||
printf(" ");
|
||||
|
||||
if (i % hexdump_cols == (hexdump_cols - 1))
|
||||
{
|
||||
for (j = i - (hexdump_cols - 1); j <= i; j++)
|
||||
{
|
||||
if (j >= len)
|
||||
putchar(' ');
|
||||
else if (isprint(((char*)mem)[j]))
|
||||
putchar(0xFF & ((char*)mem)[j]);
|
||||
else
|
||||
putchar('.');
|
||||
}
|
||||
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
logmsg(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static size_t
|
||||
https_write_cb(char *data, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
char **body;
|
||||
size_t realsize;
|
||||
|
||||
realsize = size * nmemb;
|
||||
body = (char **)userdata;
|
||||
|
||||
*body = malloc(realsize + 1);
|
||||
memcpy(*body, data, realsize);
|
||||
(*body)[realsize] = 0;
|
||||
|
||||
return realsize;
|
||||
}
|
||||
|
||||
static int
|
||||
https_get(char **body, const char *url)
|
||||
{
|
||||
CURL *curl;
|
||||
CURLcode res;
|
||||
long response_code;
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (!curl)
|
||||
{
|
||||
printf("Could not initialize CURL\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, https_write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, body);
|
||||
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK)
|
||||
{
|
||||
printf("CURL could not make request (%d)\n", (int)res);
|
||||
goto error;
|
||||
}
|
||||
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
if (response_code != 200)
|
||||
{
|
||||
printf("HTTP response code %d\n", (int)response_code);
|
||||
goto error;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
tcp_connect(const char *address, unsigned short port)
|
||||
{
|
||||
struct addrinfo hints = { 0 };
|
||||
struct addrinfo *servinfo;
|
||||
struct addrinfo *ptr;
|
||||
char strport[8];
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
|
||||
snprintf(strport, sizeof(strport), "%hu", port);
|
||||
ret = getaddrinfo(address, strport, &hints, &servinfo);
|
||||
if (ret < 0)
|
||||
{
|
||||
printf("Could not connect to %s (port %u): %s\n", address, port, gai_strerror(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (ptr = servinfo; ptr; ptr = ptr->ai_next)
|
||||
{
|
||||
fd = socket(ptr->ai_family, SOCK_STREAM, ptr->ai_protocol);
|
||||
if (fd < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = connect(fd, ptr->ai_addr, ptr->ai_addrlen);
|
||||
if (ret < 0)
|
||||
{
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
|
||||
if (!ptr)
|
||||
{
|
||||
printf("Could not connect to '%s' (port %u): %s\n", address, port, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void
|
||||
tcp_disconnect(int fd)
|
||||
{
|
||||
if (fd < 0)
|
||||
return;
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void
|
||||
progress_cb(int fd, void *arg, size_t received, size_t len)
|
||||
{
|
||||
printf("Progress on fd %d is %zu/%zu\n", fd, received, len);
|
||||
}
|
||||
|
||||
// This thread
|
||||
static void
|
||||
audio_read_cb(int fd, short what, void *arg)
|
||||
{
|
||||
int got;
|
||||
|
||||
got = evbuffer_read(audio_buf, fd, -1);
|
||||
if (got <= 0)
|
||||
{
|
||||
printf("Playback ended (%d)\n", got);
|
||||
event_base_loopbreak(evbase);
|
||||
return;
|
||||
}
|
||||
|
||||
total_bytes += got;
|
||||
|
||||
printf("Got %d bytes of audio, total received is %d bytes\n", got, total_bytes);
|
||||
|
||||
evbuffer_write(audio_buf, test_file);
|
||||
}
|
||||
|
||||
struct sp_callbacks callbacks =
|
||||
{
|
||||
.https_get = https_get,
|
||||
.tcp_connect = tcp_connect,
|
||||
.tcp_disconnect = tcp_disconnect,
|
||||
|
||||
.thread_name_set = NULL,
|
||||
|
||||
.hexdump = hexdump,
|
||||
.logmsg = logmsg,
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, char * argv[])
|
||||
{
|
||||
struct sp_session *session = NULL;
|
||||
struct sp_sysinfo sysinfo;
|
||||
struct sp_credentials credentials;
|
||||
struct sp_metadata metadata;
|
||||
struct event *read_ev;
|
||||
// struct event *stop_ev;
|
||||
// struct timeval tv = { 0 };
|
||||
int ret;
|
||||
|
||||
if (argc != 4)
|
||||
{
|
||||
printf("%s spotify_path username password|token\n", argv[0]);
|
||||
goto error;
|
||||
}
|
||||
|
||||
test_file = open("testfile.ogg", O_CREAT | O_RDWR, 0664);
|
||||
if (test_file < 0)
|
||||
{
|
||||
printf("Error opening file: %s\n", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
snprintf(sysinfo.client_name, sizeof(sysinfo.client_name), "myclient");
|
||||
snprintf(sysinfo.client_version, sizeof(sysinfo.client_version), "0.1");
|
||||
snprintf(sysinfo.client_build_id, sizeof(sysinfo.client_build_id), "a");
|
||||
snprintf(sysinfo.device_id, sizeof(sysinfo.device_id), "aabbccddeeff");
|
||||
|
||||
ret = librespotc_init(&sysinfo, &callbacks);
|
||||
if (ret < 0)
|
||||
{
|
||||
printf("Error initializing Spotify: %s\n", librespotc_last_errmsg());
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (strlen(argv[3]) < 100)
|
||||
session = librespotc_login_password(argv[2], argv[3]);
|
||||
else
|
||||
session = librespotc_login_token(argv[2], argv[3]); // Length of token should be 194
|
||||
if (!session)
|
||||
{
|
||||
printf("Error logging in: %s\n", librespotc_last_errmsg());
|
||||
goto error;
|
||||
}
|
||||
|
||||
printf("\n --- Login OK --- \n");
|
||||
|
||||
ret = librespotc_credentials_get(&credentials, session);
|
||||
if (ret < 0)
|
||||
{
|
||||
printf("Error getting session credentials: %s\n", librespotc_last_errmsg());
|
||||
goto error;
|
||||
}
|
||||
|
||||
printf("Username is %s\n", credentials.username);
|
||||
|
||||
audio_fd = librespotc_open(argv[1], session);
|
||||
if (audio_fd < 0)
|
||||
{
|
||||
printf("Error opening file: %s\n", librespotc_last_errmsg());
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = librespotc_metadata_get(&metadata, audio_fd);
|
||||
if (ret < 0)
|
||||
{
|
||||
printf("Error getting track metadata: %s\n", librespotc_last_errmsg());
|
||||
goto error;
|
||||
}
|
||||
|
||||
printf("File is open, length is %zu\n", metadata.file_len);
|
||||
|
||||
ret = librespotc_seek(audio_fd, 1000000);
|
||||
if (ret < 0)
|
||||
{
|
||||
printf("Error seeking: %s\n", librespotc_last_errmsg());
|
||||
goto error;
|
||||
}
|
||||
|
||||
evbase = event_base_new();
|
||||
audio_buf = evbuffer_new();
|
||||
|
||||
read_ev = event_new(evbase, audio_fd, EV_READ | EV_PERSIST, audio_read_cb, NULL);
|
||||
event_add(read_ev, NULL);
|
||||
|
||||
librespotc_write(audio_fd, progress_cb, NULL);
|
||||
|
||||
// stop_ev = evtimer_new(evbase, stop, &audio_fd);
|
||||
// tv.tv_sec = 2;
|
||||
// event_add(stop_ev, &tv);
|
||||
|
||||
event_base_dispatch(evbase);
|
||||
|
||||
// event_free(stop_ev);
|
||||
event_free(read_ev);
|
||||
|
||||
evbuffer_free(audio_buf);
|
||||
|
||||
event_base_free(evbase);
|
||||
|
||||
librespotc_close(audio_fd);
|
||||
|
||||
close(test_file);
|
||||
|
||||
librespotc_logout(session);
|
||||
|
||||
librespotc_deinit();
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (audio_fd >= 0)
|
||||
librespotc_close(audio_fd);
|
||||
if (test_file >= 0)
|
||||
close(test_file);
|
||||
if (session)
|
||||
librespotc_logout(session);
|
||||
|
||||
librespotc_deinit();
|
||||
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user