Merge branch 'spotifyc5'

This commit is contained in:
ejurgensen 2021-05-31 23:27:50 +02:00
commit a1f9e10f9c
82 changed files with 21304 additions and 465 deletions

View File

@ -35,13 +35,14 @@ simultaneous streaming to multiple DAAP clients.
Optional packages:
Feature | Configure argument | Packages
--------------------|--------------------------|------------------------------------------------
Chromecast | `--enable-chromecast` | libgnutls*-dev libprotobuf-c-dev
Spotify | `--enable-spotify` | libspotify-dev
Player web UI | `--disable-webinterface` | libwebsockets-dev
Live web UI | `--with-libwebsockets` | libwebsockets-dev
Pulseaudio | `--with-pulseaudio` | libpulse-dev
Feature | Configure argument | Packages
---------------------|--------------------------|------------------------------------------------
Chromecast | `--enable-chromecast` | libgnutls*-dev libprotobuf-c-dev
Spotify (built-in) | `--disable-spotify` | libprotobuf-c-dev
Spotify (libspotify) | `--enable-libspotify` | libspotify-dev
Player web UI | `--disable-webinterface` | libwebsockets-dev
Live web UI | `--with-libwebsockets` | libwebsockets-dev
Pulseaudio | `--with-pulseaudio` | libpulse-dev
Then run the following (adding configure arguments for optional features):
@ -266,7 +267,7 @@ Libraries:
- libpulse (optional - Pulseaudio local audio)
from <https://www.freedesktop.org/wiki/Software/PulseAudio/Download/>
- libspotify (optional - Spotify support)
from <https://developer.spotify.com>
(deprecated by Spotify)
- libgnutls (optional - Chromecast support)
from <http://www.gnutls.org/>
- libprotobuf-c (optional - Chromecast support)
@ -310,11 +311,16 @@ needed.
To display the configure options `run ./configure --help`.
Support for Spotify is optional. Use `--enable-spotify` to enable this feature.
If you enable this feature `libspotify/api.h` is required at compile time.
Forked-daapd uses runtime dynamic linking to the libspotify library, so even
though you compiled with `--enable-spotify`, the executable will still be able
to run on systems without libspotify (the Spotify features will then be disabled).
Support for Spotify is optional. Use `--disable-spotify` to disable this feature.
OwnTone supports two ways of integrating with Spotify: Using its own, built-in
integration layer (which is the default), or to use Spotify's deprecated
libspotify. To enable the latter, you must configure with `--enable-libspotify`
and also make sure libspotify's `libspotify/api.h` is installed at compile time.
At runtime, libspotify must be installed, and `use_libspotify` must be enabled
in owntone.conf. OwnTone uses runtime dynamic linking to the libspotify library,
so even though you compiled with `--enable-libspotify`, the executable will
still be able to run on systems without libspotify. If you only want libspotify
integration, you can use `--disable-spotify` and `--enable-libspotify`.
Support for LastFM scrobbling is optional. Use `--enable-lastfm` to enable this
feature.

View File

@ -1,3 +1,7 @@
if COND_LIBRESPOTC
LIBRESPOTC_SUBDIR=src/inputs/librespot-c
endif
ACLOCAL_AMFLAGS = -I m4
RPM_SPEC_FILE = owntone.spec
@ -8,7 +12,7 @@ sysconf_DATA = $(CONF_FILE)
BUILT_SOURCES = $(CONF_FILE) $(SYSTEMD_SERVICE_FILE)
SUBDIRS = sqlext src htdocs
SUBDIRS = $(LIBRESPOTC_SUBDIR) sqlext src htdocs
dist_man_MANS = owntone.8

View File

@ -473,44 +473,30 @@ curl "http://localhost:3689/logout?session-id=50"
## Spotify
### Via libspotify
OwnTone has built-in support for playback of the tracks in your Spotify library.
OwnTone has support for playback of the tracks in your Spotify library.
You must have a Spotify premium account. If you normally log into Spotify with
your Facebook account you must first go to Spotify's web site where you can get
the Spotify username and password that matches your account.
1. Go to the [web interface](http://owntone.local:3689) and check that your
version of OwnTone was built with Spotify support.
2. You must have a Spotify premium account. If you normally log into Spotify
with your Facebook account you must first go to Spotify's web site where you
can get the Spotify username and password that matches your account.
3. Make sure you have `libspotify` installed. Unfortunately, it is no longer
available from Spotify, and at the time of writing this they have not
provided an alternative. However, on most Debian-based platforms, you can
still get it like this:
- Add the mopidy repository, see [instructions](https://apt.mopidy.com)
- Install with `apt install libspotify-dev`
You must also make sure that your browser can reach OwnTone's web interface via
the address [http://owntone.local:3689](http://owntone.local:3689). Try it right
now! That is where Spotify's OAuth page will redirect your browser with the
token that OwnTone needs, so it must work. The address is announced by the
server via mDNS, but if that for some reason doesn't work then configure it via
router or .hosts file. You can remove it again after completing the login.
Once the above is in order you can login to Spotify via the web interface. The
procedure for logging in to Spotify is a two-step procedure due to the current
state of libspotify, but the web interface makes both steps available to you.
To authorize OwnTone, open the web interface, locate Settings > Online Services
and then click the Authorize button. You will then be sent to Spotify's
authorization service, which will send you back to the web interface after
you have given the authorization.
Note that the address [http://owntone.local:3689](http://owntone.local:3689)
must be working on your local network to complete the Spotify OAuth web login.
The address is announced automatically via mDNS, but if that for some reason
doesn't work then configure it via router or .hosts file. You can remove it
again after completing the login. This is needed because the redirect_uri
parameter of the Spotify token request is to this address.
Spotify no longer automatically notifies clients about playlist updates, so you
Spotify no longer automatically notifies clients about library updates, so you
have to trigger updates manually. You can for instance set up a cron job that
runs `/usr/bin/curl http://localhost:3689/api/update`
OwnTone will not store your password, but will still be able to log you in
automatically afterwards, because libspotify saves a login token. You can
configure the location of your Spotify user data in the configuration file.
To permanently logout and remove Spotify tracks + credentials make a request to
[http://[your_server_address_here]:3689/api/spotify-logout](http://[your_server_address_here]:3689/api/spotify-logout)
and also delete the contents of `/var/cache/owntone/libspotify`.
To logout and remove Spotify tracks + credentials make a request to
[http://[your_server_address_here]:3689/api/spotify-logout](http://[your_server_address_here]:3689/api/spotify-logout).
Limitations:
You will not be able to do any playlist management through OwnTone - use
@ -540,6 +526,13 @@ pipe = "/srv/music/spotify"
metadataPipe = "/srv/music/spotify.metadata"
```
### Via libspotify
This method is being deprecated, but is still available if the server was built
with it, libspotify is installed and `use_libspotify` is enabled in the config
file. Please consult [previous README versions](#references) for details on
using libspotify.
## LastFM

View File

@ -286,21 +286,34 @@ AS_IF([[test "x$with_avahi" = "xno"]],
[AC_MSG_ERROR([[Avahi client or Bonjour DNS_SD required, please install one.]])])])
AM_CONDITIONAL([COND_AVAHI], [[test "x$with_avahi" = "xyes"]])
dnl Spotify with dynamic linking to libspotify
OWNTONE_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
dnl Spotify support
OWNTONE_ARG_DISABLE([Spotify support], [spotify], [SPOTIFY_LIBRESPOTC],
[OWNTONE_MODULES_CHECK([OWNTONE_OPTS], [LIBPROTOBUF_C],
[libprotobuf-c >= 1.0.0], [protobuf_c_message_pack],
[protobuf-c/protobuf-c.h])
OWNTONE_VAR_PREPEND([OWNTONE_OPTS_LIBS], [inputs/librespot-c/librespot-c.a])
AC_DEFINE([SPOTIFY], 1,
[Define to 1 to enable Spotify])
])
AM_CONDITIONAL([COND_LIBRESPOTC], [[test "x$enable_spotify" = "xyes"]])
dnl Spotify with dynamic linking to libspotify (legacy)
OWNTONE_ARG_ENABLE([legacy libspotify support], [libspotify], [SPOTIFY_LIBSPOTIFY],
[AS_IF([[test "x$with_libevent_pthreads" = "xno"]],
[AC_MSG_ERROR([[Spotify support requires libevent_pthreads]])])
OWNTONE_MODULES_CHECK([SPOTIFY], [LIBSPOTIFY], [libspotify],
[AC_MSG_ERROR([[libspotify support requires libevent_pthreads]])])
OWNTONE_MODULES_CHECK([SPOTIFY_LIBSPOTIFY], [LIBSPOTIFY], [libspotify],
[], [libspotify/api.h])
AC_DEFINE([HAVE_SPOTIFY_H], 1,
[Define to 1 if you have the <libspotify/api.h> header file.])
dnl Don't link with libspotify, use dynamic linking
AC_SEARCH_LIBS([dlopen], [dl], [],
[AC_MSG_ERROR([[Spotify support requires dlopen]])])
OWNTONE_VAR_PREPEND([OWNTONE_OPTS_CPPFLAGS], [$SPOTIFY_CPPFLAGS])
[AC_MSG_ERROR([[libspotify support requires dlopen]])])
OWNTONE_VAR_PREPEND([OWNTONE_OPTS_CPPFLAGS], [$SPOTIFY_LIBSPOTIFY_CPPFLAGS])
OWNTONE_VAR_PREPEND([OWNTONE_OPTS_LIBS], [-rdynamic])
AC_DEFINE([SPOTIFY], 1,
[Define to 1 to enable Spotify])
])
AM_CONDITIONAL([COND_SPOTIFY], [[test "x$enable_spotify" = "xyes"]])
AM_CONDITIONAL([COND_LIBSPOTIFY], [[test "x$enable_libspotify" = "xyes"]])
AM_CONDITIONAL([COND_SPOTIFY], [[test "x$enable_spotify" = "xyes" -o "x$enable_libspotify" = "xyes"]])
dnl LastFM support
OWNTONE_ARG_DISABLE([LastFM support], [lastfm], [LASTFM])
@ -359,6 +372,11 @@ OWNTONE_GROUP=${withval:-$OWNTONE_USER}
AC_SUBST([OWNTONE_GROUP])
dnl --- End options ---
dnl Unconditional since we always want to produce Makefiles for dist targets
AC_CONFIG_SUBDIRS([
src/inputs/librespot-c
])
AC_CONFIG_FILES([
src/Makefile
sqlext/Makefile

View File

@ -346,10 +346,16 @@ audio {
# Spotify settings (only have effect if Spotify enabled - see README/INSTALL)
spotify {
# The server can stream from Spotify using either its own implementation
# or using Spotify's libspotify (which was deprecated many years ago)
# use_libspotify = false
# Directory where user settings should be stored (credentials)
# (only has effect with libspotify)
# settings_dir = "@localstatedir@/cache/@PACKAGE@/libspotify"
# Cache directory
# (only has effect with libspotify)
# cache_dir = "/tmp"
# Set preferred bitrate for music streaming

View File

@ -2,7 +2,17 @@
sbin_PROGRAMS = owntone
if COND_SPOTIFY
SPOTIFY_SRC=spotify.c spotify.h spotify_webapi.c spotify_webapi.h inputs/spotify.c
SPOTIFY_SRC = \
library/spotify_webapi.c library/spotify_webapi.h inputs/spotify.c inputs/spotify.h
endif
if COND_LIBRESPOTC
LIBRESPOTC_SRC = \
inputs/spotify_librespotc.c
endif
if COND_LIBSPOTIFY
LIBSPOTIFY_SRC = \
inputs/spotify_libspotify.c \
inputs/libspotify/libspotify.c inputs/libspotify/libspotify.h
endif
if COND_LASTFM
@ -104,7 +114,6 @@ owntone_SOURCES = main.c \
library.c library.h \
$(MDNS_SRC) mdns.h \
remote_pairing.c remote_pairing.h \
avio_evbuffer.c avio_evbuffer.h \
httpd.c httpd.h \
httpd_rsp.c httpd_rsp.h \
httpd_daap.c httpd_daap.h \
@ -134,7 +143,7 @@ owntone_SOURCES = main.c \
outputs/streaming.c outputs/dummy.c outputs/fifo.c \
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
$(SPOTIFY_SRC) \
$(SPOTIFY_SRC) $(LIBRESPOTC_SRC) $(LIBSPOTIFY_SRC) \
$(LASTFM_SRC) \
$(MPD_SRC) \
listener.c listener.h \

View File

@ -45,8 +45,8 @@
#include "artwork.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify_webapi.h"
#ifdef SPOTIFY
# include "library/spotify_webapi.h"
#endif
/* This artwork module will look for artwork by consulting a set of sources one
@ -605,6 +605,7 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is
{
struct decode_ctx *xcode_decode = NULL;
struct encode_ctx *xcode_encode = NULL;
struct transcode_evbuf_io xcode_evbuf_io = { 0 };
struct evbuffer *xcode_buf = NULL;
void *frame;
int src_width;
@ -633,9 +634,15 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is
ret = ART_E_ERROR;
goto out;
}
xcode_evbuf_io.evbuf = xcode_buf;
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, data_kind, NULL, &xcode_evbuf_io, 0); // Covers XCODE_PNG too
}
else
{
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, data_kind, path, NULL, 0); // Covers XCODE_PNG too
}
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, data_kind, path, xcode_buf, 0); // Covers XCODE_PNG too
if (!xcode_decode)
{
if (path)
@ -1608,7 +1615,7 @@ source_item_coverartarchive_get(struct artwork_ctx *ctx)
return ret;
}
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY
static int
source_item_spotifywebapi_track_get(struct artwork_ctx *ctx)
{

View File

@ -1,140 +0,0 @@
/*
* Copyright (C) 2011 Julien BLACHE <jb@jblache.org>
*
* 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
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <libavformat/avformat.h>
#include "logger.h"
#include "avio_evbuffer.h"
/*
* libav AVIO interface for evbuffers
*/
#define BUFFER_SIZE 4096
struct avio_evbuffer {
struct evbuffer *evbuf;
uint8_t *buffer;
};
static int
avio_evbuffer_read(void *opaque, uint8_t *buf, int size)
{
struct avio_evbuffer *ae;
int ret;
ae = (struct avio_evbuffer *)opaque;
ret = evbuffer_remove(ae->evbuf, buf, size);
// Must return AVERROR, see avio.h: avio_alloc_context()
return (ret > 0) ? ret : AVERROR_EOF;
}
static int
avio_evbuffer_write(void *opaque, uint8_t *buf, int size)
{
struct avio_evbuffer *ae;
int ret;
ae = (struct avio_evbuffer *)opaque;
ret = evbuffer_add(ae->evbuf, buf, size);
return (ret == 0) ? size : -1;
}
static AVIOContext *
avio_evbuffer_open(struct evbuffer *evbuf, int is_output)
{
struct avio_evbuffer *ae;
AVIOContext *s;
ae = (struct avio_evbuffer *)malloc(sizeof(struct avio_evbuffer));
if (!ae)
{
DPRINTF(E_LOG, L_FFMPEG, "Out of memory for avio_evbuffer\n");
return NULL;
}
ae->buffer = av_mallocz(BUFFER_SIZE);
if (!ae->buffer)
{
DPRINTF(E_LOG, L_FFMPEG, "Out of memory for avio buffer\n");
free(ae);
return NULL;
}
ae->evbuf = evbuf;
if (is_output)
s = avio_alloc_context(ae->buffer, BUFFER_SIZE, 1, ae, NULL, avio_evbuffer_write, NULL);
else
s = avio_alloc_context(ae->buffer, BUFFER_SIZE, 0, ae, avio_evbuffer_read, NULL, NULL);
if (!s)
{
DPRINTF(E_LOG, L_FFMPEG, "Could not allocate AVIOContext\n");
av_free(ae->buffer);
free(ae);
return NULL;
}
s->seekable = 0;
return s;
}
AVIOContext *
avio_input_evbuffer_open(struct evbuffer *evbuf)
{
return avio_evbuffer_open(evbuf, 0);
}
AVIOContext *
avio_output_evbuffer_open(struct evbuffer *evbuf)
{
return avio_evbuffer_open(evbuf, 1);
}
void
avio_evbuffer_close(AVIOContext *s)
{
struct avio_evbuffer *ae;
if (!s)
return;
ae = (struct avio_evbuffer *)s->opaque;
avio_flush(s);
av_free(s->buffer);
free(ae);
av_free(s);
}

View File

@ -1,16 +0,0 @@
#ifndef __AVIO_EVBUFFER_H__
#define __AVIO_EVBUFFER_H__
#include <event2/buffer.h>
AVIOContext *
avio_input_evbuffer_open(struct evbuffer *evbuf);
AVIOContext *
avio_output_evbuffer_open(struct evbuffer *evbuf);
void
avio_evbuffer_close(AVIOContext *s);
#endif /* !__AVIO_EVBUFFER_H__ */

View File

@ -188,6 +188,7 @@ static cfg_opt_t sec_fifo[] =
/* Spotify section structure */
static cfg_opt_t sec_spotify[] =
{
CFG_BOOL("use_libspotify", cfg_false, CFGF_NONE),
CFG_STR("settings_dir", STATEDIR "/cache/" PACKAGE "/libspotify", CFGF_NONE),
CFG_STR("cache_dir", "/tmp", CFGF_NONE),
CFG_INT("bitrate", 0, CFGF_NONE),

View File

@ -55,9 +55,9 @@
#include "remote_pairing.h"
#include "settings.h"
#include "smartpl_query.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify_webapi.h"
# include "spotify.h"
#ifdef SPOTIFY
# include "library/spotify_webapi.h"
# include "inputs/spotify.h"
#endif
@ -1201,11 +1201,11 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
CHECK_NULL(L_WEB, jreply = json_object_new_object());
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY
int httpd_port;
char redirect_uri[256];
char *oauth_uri;
struct spotify_status_info info;
struct spotify_status sp_status;
struct spotifywebapi_status_info webapi_info;
struct spotifywebapi_access_token webapi_token;
@ -1225,10 +1225,10 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
json_object_object_add(jreply, "oauth_uri", json_object_new_string(oauth_uri));
free(oauth_uri);
spotify_status_info_get(&info);
json_object_object_add(jreply, "libspotify_installed", json_object_new_boolean(info.libspotify_installed));
json_object_object_add(jreply, "libspotify_logged_in", json_object_new_boolean(info.libspotify_logged_in));
safe_json_add_string(jreply, "libspotify_user", info.libspotify_user);
spotify_status_get(&sp_status);
json_object_object_add(jreply, "libspotify_installed", json_object_new_boolean(sp_status.installed));
json_object_object_add(jreply, "libspotify_logged_in", json_object_new_boolean(sp_status.logged_in));
safe_json_add_string(jreply, "libspotify_user", sp_status.username);
spotifywebapi_status_info_get(&webapi_info);
json_object_object_add(jreply, "webapi_token_valid", json_object_new_boolean(webapi_info.token_valid));
@ -1255,12 +1255,12 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
static int
jsonapi_reply_spotify_login(struct httpd_request *hreq)
{
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY
struct evbuffer *in_evbuf;
json_object* request;
const char *user;
const char *password;
char *errmsg = NULL;
const char *errmsg;
json_object* jreply;
json_object* errors;
int ret;
@ -1282,7 +1282,7 @@ jsonapi_reply_spotify_login(struct httpd_request *hreq)
password = jparse_str_from_obj(request, "password");
if (user && strlen(user) > 0 && password && strlen(password) > 0)
{
ret = spotify_login_user(user, password, &errmsg);
ret = spotify_login(user, password, &errmsg);
if (ret < 0)
{
json_object_object_add(jreply, "success", json_object_new_boolean(false));
@ -1294,7 +1294,6 @@ jsonapi_reply_spotify_login(struct httpd_request *hreq)
{
json_object_object_add(jreply, "success", json_object_new_boolean(true));
}
free(errmsg);
}
else
{
@ -1323,7 +1322,8 @@ jsonapi_reply_spotify_login(struct httpd_request *hreq)
static int
jsonapi_reply_spotify_logout(struct httpd_request *hreq)
{
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY
spotifywebapi_purge();
spotify_logout();
#endif
return HTTP_NOCONTENT;

View File

@ -32,19 +32,19 @@
#include "logger.h"
#include "misc.h"
#include "conffile.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify_webapi.h"
#ifdef SPOTIFY
# include "library/spotify_webapi.h"
#endif
/* --------------------------- REPLY HANDLERS ------------------------------- */
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY
static int
oauth_reply_spotify(struct httpd_request *hreq)
{
char redirect_uri[256];
char *errmsg;
const char *errmsg;
int httpd_port;
int ret;
@ -56,7 +56,6 @@ oauth_reply_spotify(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback: '%s'\n", hreq->uri_parsed->uri);
httpd_send_error(hreq->req, HTTP_INTERNAL, errmsg);
free(errmsg);
return -1;
}

View File

@ -58,9 +58,12 @@ extern struct input_definition input_file;
extern struct input_definition input_http;
extern struct input_definition input_pipe;
extern struct input_definition input_timer;
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY_LIBRESPOTC
extern struct input_definition input_spotify;
#endif
#ifdef SPOTIFY_LIBSPOTIFY
extern struct input_definition input_libspotify;
#endif
// Must be in sync with enum input_types
static struct input_definition *inputs[] = {
@ -68,8 +71,11 @@ static struct input_definition *inputs[] = {
&input_http,
&input_pipe,
&input_timer,
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY_LIBRESPOTC
&input_spotify,
#endif
#ifdef SPOTIFY_LIBSPOTIFY
&input_libspotify,
#endif
NULL
};
@ -171,10 +177,16 @@ map_data_kind(int data_kind)
case DATA_KIND_PIPE:
return INPUT_TYPE_PIPE;
#ifdef HAVE_SPOTIFY_H
case DATA_KIND_SPOTIFY:
return INPUT_TYPE_SPOTIFY;
#ifdef SPOTIFY_LIBRESPOTC
if (!inputs[INPUT_TYPE_SPOTIFY]->disabled)
return INPUT_TYPE_SPOTIFY;
#endif
#ifdef SPOTIFY_LIBSPOTIFY
if (!inputs[INPUT_TYPE_LIBSPOTIFY]->disabled)
return INPUT_TYPE_LIBSPOTIFY;
#endif
return -1;
default:
return -1;
@ -420,6 +432,7 @@ setup(struct input_source *source, struct db_queue_item *queue_item, int seek_ms
source->id = queue_item->file_id;
source->len_ms = queue_item->song_length;
source->path = safe_strdup(queue_item->path);
source->evbase = evbase_input;
DPRINTF(E_DBG, L_PLAYER, "Setting up input item '%s' (item id %" PRIu32 ")\n", source->path, source->item_id);
@ -457,6 +470,8 @@ start(void *arg, int *retval)
struct db_queue_item *queue_item;
int ret;
DPRINTF(E_WARN, L_PLAYER, "now %d, item_id %d, now item_id %d\n", input_now_reading.open, cmdarg->item_id, input_now_reading.item_id);
// If we are asked to start the item that is currently open we can just seek
if (input_now_reading.open && cmdarg->item_id == input_now_reading.item_id)
{
@ -768,7 +783,7 @@ input_read(void *data, size_t size, short *flag, void **flagdata)
if (*flag || (debug_elapsed > 10 * one_sec_size))
{
debug_elapsed = 0;
DPRINTF(E_DBG, L_PLAYER, "READ %zu bytes (%d/%d/%d), WROTE %zu bytes (%d/%d/%d), SIZE %zu (=%zu), FLAGS %04x\n",
DPRINTF(E_DBG, L_PLAYER, "READ %zu bytes (%d/%d/%d), WROTE %zu bytes (%d/%d/%d), DIFF %zu, SIZE %zu/%d, FLAGS %04x\n",
input_buffer.bytes_read,
input_buffer.cur_read_quality.sample_rate,
input_buffer.cur_read_quality.bits_per_sample,
@ -777,8 +792,9 @@ input_read(void *data, size_t size, short *flag, void **flagdata)
input_buffer.cur_write_quality.sample_rate,
input_buffer.cur_write_quality.bits_per_sample,
input_buffer.cur_write_quality.channels,
evbuffer_get_length(input_buffer.evbuf),
input_buffer.bytes_written - input_buffer.bytes_read,
evbuffer_get_length(input_buffer.evbuf),
INPUT_BUFFER_THRESHOLD,
*flag);
}
#endif

View File

@ -16,9 +16,12 @@ enum input_types
INPUT_TYPE_HTTP,
INPUT_TYPE_PIPE,
INPUT_TYPE_TIMER,
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY_LIBRESPOTC
INPUT_TYPE_SPOTIFY,
#endif
#ifdef SPOTIFY_LIBSPOTIFY
INPUT_TYPE_LIBSPOTIFY,
#endif
};
enum input_flags
@ -62,6 +65,10 @@ struct input_source
// Opaque pointer to data that the input backend sets up when start() is
// called, and that is cleaned up by the backend when stop() is called
void *input_ctx;
// The input's event base that the input backend can use for own events
struct event_base *evbase;
// Private evbuf. Alloc'ed by backend at start() and free'd at stop()
struct evbuffer *evbuf;
// Private source quality storage
@ -128,7 +135,7 @@ struct input_definition
/* ---------------------- Interface towards input backends ------------------ */
/* Thread: input and spotify */
/* Thread: input */
/*
* Transfer stream data to the player's input buffer. Data must be PCM-LE

38
src/inputs/librespot-c/.gitignore vendored Normal file
View 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

View 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.

View 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

View 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)

View 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

View 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__ */

View 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;
}

View 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);

View 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, &current_cmd->ret);
cmdbase->current_cmd = NULL;
/* Process commands again */
event_add(cmdbase->command_event, NULL);
pthread_cond_signal(&current_cmd->cond);
pthread_mutex_unlock(&current_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);
}

View 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_ */

File diff suppressed because it is too large Load Diff

View 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);

View 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;
}

View 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__ */

View 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__

View 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;
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

View 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 */

View 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;
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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;
}

View 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] */
};

View 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 */

View 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;
}

View File

@ -0,0 +1,10 @@
syntax = "proto2";
message MergedProfileRequest {
}
message MergedProfileReply {
optional string username = 0x1;
optional string artistid = 0x2;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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;
}

View 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;
}

View 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;
}

View 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;
}
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@ -0,0 +1,8 @@
syntax = "proto2";
message Subscription {
optional string uri = 0x1;
optional int32 expiry = 0x2;
optional int32 status_code = 0x3;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@ -0,0 +1,6 @@
syntax = "proto2";
message Toplist {
repeated string items = 0x1;
}

View 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 */

View 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;
}
}
}

View 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

View File

@ -0,0 +1 @@
test1

View 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

View 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;
}

View File

@ -43,15 +43,15 @@
#include <libspotify/api.h>
#include <json.h>
#include "spotify.h"
#include "spotify_webapi.h"
#include "libspotify.h"
#include "library.h"
#include "library/spotify_webapi.h"
#include "logger.h"
#include "misc.h"
#include "http.h"
#include "conffile.h"
#include "cache.h"
#include "commands.h"
#include "library.h"
#include "input.h"
#include "listener.h"
@ -86,6 +86,9 @@ struct artwork_get_param
int is_loaded;
};
static void
libspotify_playback_stop_nonblock(void);
/* --- Globals --- */
// Spotify thread
static pthread_t tid_spotify;
@ -604,7 +607,7 @@ playback_setup(void *arg, int *retval)
if (SP_ERROR_OK != err)
{
DPRINTF(E_LOG, L_SPOTIFY, "Playback setup failed: %s\n", fptr_sp_error_message(err));
*retval = (SP_ERROR_IS_LOADING == err) ? SPOTIFY_SETUP_ERROR_IS_LOADING : -1;
*retval = (SP_ERROR_IS_LOADING == err) ? LIBSPOTIFY_SETUP_ERROR_IS_LOADING : -1;
return COMMAND_END;
}
@ -1014,7 +1017,7 @@ static int music_delivery(sp_session *sess, const sp_audioformat *format,
if ((format->sample_type != SP_SAMPLETYPE_INT16_NATIVE_ENDIAN) || (format->channels != 2))
{
DPRINTF(E_LOG, L_SPOTIFY, "Got music with unsupported sample format or number of channels, stopping playback\n");
spotify_playback_stop_nonblock();
libspotify_playback_stop_nonblock();
return num_frames;
}
@ -1081,7 +1084,7 @@ static void play_token_lost(sp_session *sess)
{
DPRINTF(E_LOG, L_SPOTIFY, "Music interrupted - some other session is playing on the account\n");
spotify_playback_stop_nonblock();
libspotify_playback_stop_nonblock();
}
static void connectionstate_updated(sp_session *session)
@ -1093,7 +1096,7 @@ static void connectionstate_updated(sp_session *session)
else if (g_state == SPOTIFY_STATE_PLAYING)
{
DPRINTF(E_LOG, L_SPOTIFY, "Music interrupted - connection error or logged out\n");
spotify_playback_stop_nonblock();
libspotify_playback_stop_nonblock();
}
}
@ -1214,7 +1217,7 @@ notify_cb(int fd, short what, void *arg)
/* Thread: player */
int
spotify_playback_setup(const char *path)
libspotify_playback_setup(const char *path)
{
sp_link *link;
@ -1231,7 +1234,7 @@ spotify_playback_setup(const char *path)
}
int
spotify_playback_play()
libspotify_playback_play()
{
DPRINTF(E_DBG, L_SPOTIFY, "Playback request\n");
@ -1239,7 +1242,7 @@ spotify_playback_play()
}
int
spotify_playback_pause()
libspotify_playback_pause()
{
DPRINTF(E_DBG, L_SPOTIFY, "Pause request\n");
@ -1248,7 +1251,7 @@ spotify_playback_pause()
/* Thread: libspotify */
void
spotify_playback_pause_nonblock(void)
libspotify_playback_pause_nonblock(void)
{
DPRINTF(E_DBG, L_SPOTIFY, "Nonblock pause request\n");
@ -1257,7 +1260,7 @@ spotify_playback_pause_nonblock(void)
/* Thread: player and libspotify */
int
spotify_playback_stop(void)
libspotify_playback_stop(void)
{
DPRINTF(E_DBG, L_SPOTIFY, "Stop request\n");
@ -1266,7 +1269,7 @@ spotify_playback_stop(void)
/* Thread: player and libspotify */
void
spotify_playback_stop_nonblock(void)
libspotify_playback_stop_nonblock(void)
{
DPRINTF(E_DBG, L_SPOTIFY, "Nonblock stop request\n");
@ -1275,7 +1278,7 @@ spotify_playback_stop_nonblock(void)
/* Thread: player */
int
spotify_playback_seek(int ms)
libspotify_playback_seek(int ms)
{
int ret;
@ -1289,7 +1292,7 @@ spotify_playback_seek(int ms)
/* Thread: httpd (artwork) and worker */
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
libspotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
{
struct artwork_get_param artwork;
struct timespec ts;
@ -1324,7 +1327,7 @@ spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
/* Thread: httpd */
void
spotify_uri_register(const char *uri)
libspotify_uri_register(const char *uri)
{
char *tmp;
@ -1333,7 +1336,7 @@ spotify_uri_register(const char *uri)
}
void
spotify_status_info_get(struct spotify_status_info *info)
libspotify_status_info_get(struct spotify_status_info *info)
{
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&status_lck));
memcpy(info, &spotify_status_info, sizeof(struct spotify_status_info));
@ -1342,7 +1345,7 @@ spotify_status_info_get(struct spotify_status_info *info)
/* Thread: library, httpd */
static int
logout(char **errmsg)
logout(const char **errmsg)
{
sp_error err;
@ -1362,7 +1365,7 @@ logout(char **errmsg)
{
DPRINTF(E_LOG, L_SPOTIFY, "Could not logout of Spotify: %s\n", fptr_sp_error_message(err));
if (errmsg)
*errmsg = safe_asprintf("Could not logout of Spotify: %s", fptr_sp_error_message(err));
*errmsg = fptr_sp_error_message(err);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
return -1;
@ -1376,7 +1379,7 @@ logout(char **errmsg)
/* Thread: library, httpd */
static int
login_user(const char *user, const char *password, char **errmsg)
login_user(const char *user, const char *password, const char **errmsg)
{
sp_error err;
int ret;
@ -1387,13 +1390,13 @@ login_user(const char *user, const char *password, char **errmsg)
{
DPRINTF(E_LOG, L_SPOTIFY, "Can't login! - could not find libspotify\n");
if (errmsg)
*errmsg = safe_asprintf("Could not find libspotify");
*errmsg = "Could not find libspotify";
}
else
{
DPRINTF(E_LOG, L_SPOTIFY, "Can't login! - no valid Spotify session\n");
if (errmsg)
*errmsg = safe_asprintf("No valid Spotify session");
*errmsg = "No valid Spotify session";
}
return -1;
@ -1420,7 +1423,7 @@ login_user(const char *user, const char *password, char **errmsg)
{
DPRINTF(E_LOG, L_SPOTIFY, "Could not login into Spotify: %s\n", fptr_sp_error_message(err));
if (errmsg)
*errmsg = safe_asprintf("Could not login into Spotify: %s", fptr_sp_error_message(err));
*errmsg = fptr_sp_error_message(err);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
return -1;
@ -1434,14 +1437,14 @@ login_user(const char *user, const char *password, char **errmsg)
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&status_lck));
if (ret < 0 && errmsg)
*errmsg = safe_asprintf("Login failed");
*errmsg = "Login failed";
return ret;
}
/* Thread: httpd, library */
int
spotify_login_user(const char *user, const char *password, char **errmsg)
libspotify_login(const char *user, const char *password, const char **errmsg)
{
int ret;
@ -1457,32 +1460,20 @@ spotify_login_user(const char *user, const char *password, char **errmsg)
/* Thread: library */
int
spotify_relogin()
libspotify_relogin(void)
{
return login_user(NULL, NULL, NULL);
}
/* Thread: library */
void
spotify_login(char **arglist)
{
if (arglist)
spotify_login_user(arglist[0], arglist[1], NULL);
else
spotify_login_user(NULL, NULL, NULL);
}
void
spotify_logout(void)
libspotify_logout(void)
{
logout(NULL);
spotifywebapi_purge();
}
/* Thread: main */
int
spotify_init(void)
libspotify_init(void)
{
cfg_t *spotify_cfg;
sp_session *sp;
@ -1625,7 +1616,7 @@ spotify_init(void)
}
void
spotify_deinit(void)
libspotify_deinit(void)
{
int ret;

View File

@ -0,0 +1,65 @@
#ifndef __LIBSPOTIFY_H__
#define __LIBSPOTIFY_H__
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/http.h>
#include <stdbool.h>
struct spotify_status_info
{
bool libspotify_installed;
bool libspotify_logged_in;
char libspotify_user[100];
};
#define LIBSPOTIFY_SETUP_ERROR_IS_LOADING -2
int
libspotify_playback_setup(const char *path);
int
libspotify_playback_play(void);
int
libspotify_playback_pause(void);
//void
//spotify_playback_pause_nonblock(void);
int
libspotify_playback_stop(void);
//void
//spotify_playback_stop_nonblock(void);
int
libspotify_playback_seek(int ms);
//int
//spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);
int
libspotify_relogin(void);
int
libspotify_login(const char *user, const char *password, const char **errmsg);
void
libspotify_logout(void);
void
libspotify_status_info_get(struct spotify_status_info *info);
void
libspotify_uri_register(const char *uri);
int
libspotify_init(void);
void
libspotify_deinit(void);
#endif /* !__LIBSPOTIFY_H__ */

View File

@ -1,6 +1,4 @@
/*
* Copyright (C) 2017 Espen Jurgensen
*
* 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
@ -16,77 +14,133 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include "input.h"
#include "logger.h"
#include "conffile.h"
#include "spotify.h"
// How many retries to start playback if resource is still loading
#define SPOTIFY_SETUP_RETRIES 5
// How long to wait between retries in microseconds (500000 = 0.5 seconds)
#define SPOTIFY_SETUP_RETRY_WAIT 500000
#ifdef SPOTIFY_LIBRESPOTC
extern struct spotify_backend spotify_librespotc;
#endif
#ifdef SPOTIFY_LIBSPOTIFY
extern struct spotify_backend spotify_libspotify;
#endif
static int
setup(struct input_source *source)
static struct spotify_backend *
backend_set(void)
{
int i = 0;
int ret;
while((ret = spotify_playback_setup(source->path)) == SPOTIFY_SETUP_ERROR_IS_LOADING)
{
if (i >= SPOTIFY_SETUP_RETRIES)
break;
DPRINTF(E_DBG, L_SPOTIFY, "Resource still loading (%d)\n", i);
usleep(SPOTIFY_SETUP_RETRY_WAIT);
i++;
}
if (ret < 0)
return -1;
ret = spotify_playback_play();
if (ret < 0)
return -1;
return 0;
#ifdef SPOTIFY_LIBRESPOTC
if (!cfg_getbool(cfg_getsec(cfg, "spotify"), "use_libspotify"))
return &spotify_librespotc;
#endif
#ifdef SPOTIFY_LIBSPOTIFY
if (cfg_getbool(cfg_getsec(cfg, "spotify"), "use_libspotify"))
return &spotify_libspotify;
#endif
DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify configuration (not built with the configured backend)\n");
return NULL;
}
static int
stop(struct input_source *source)
/* -------------- Dispatches functions exposed via spotify.h ---------------- */
/* (probably not necessary when libspotify is removed) */
/* Called from other threads than the input thread */
int
spotify_init(void)
{
int ret;
struct spotify_backend *backend = backend_set();
ret = spotify_playback_stop();
if (ret < 0)
return -1;
if (!backend || !backend->init)
return 0; // Just a no-op
return 0;
return backend->init();
}
static int
seek(struct input_source *source, int seek_ms)
void
spotify_deinit(void)
{
int ret;
struct spotify_backend *backend = backend_set();
ret = spotify_playback_seek(seek_ms);
if (ret < 0)
return -1;
if (!backend || !backend->deinit)
return;
return ret;
backend->deinit();
}
struct input_definition input_spotify =
int
spotify_login(const char *username, const char *password, const char **errmsg)
{
.name = "Spotify",
.type = INPUT_TYPE_SPOTIFY,
.disabled = 0,
.setup = setup,
.stop = stop,
.seek = seek,
};
struct spotify_backend *backend = backend_set();
if (!backend || !backend->login)
return -1;
return backend->login(username, password, errmsg);
}
int
spotify_login_token(const char *username, const char *token, const char **errmsg)
{
struct spotify_backend *backend = backend_set();
if (!backend || !backend->login_token)
return -1;
return backend->login_token(username, token, errmsg);
}
void
spotify_logout(void)
{
struct spotify_backend *backend = backend_set();
if (!backend || !backend->logout)
return;
backend->logout();
}
int
spotify_relogin(void)
{
struct spotify_backend *backend = backend_set();
if (!backend || !backend->relogin)
return -1;
return backend->relogin();
}
void
spotify_uri_register(const char *uri)
{
struct spotify_backend *backend = backend_set();
if (!backend || !backend->uri_register)
return;
backend->uri_register(uri);
}
void
spotify_status_get(struct spotify_status *status)
{
struct spotify_backend *backend = backend_set();
memset(status, 0, sizeof(struct spotify_status));
if (!backend || !backend->status_get)
return;
backend->status_get(status);
}

50
src/inputs/spotify.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef __SPOTIFY_H__
#define __SPOTIFY_H__
#include <stdbool.h>
#include <stdint.h>
struct spotify_status
{
bool installed;
bool logged_in;
char username[128];
};
struct spotify_backend
{
int (*init)(void);
void (*deinit)(void);
int (*login)(const char *username, const char *password, const char **errmsg);
int (*login_token)(const char *username, const char *token, const char **errmsg);
void (*logout)(void);
int (*relogin)(void);
void (*uri_register)(const char *uri);
void (*status_get)(struct spotify_status *status);
};
int
spotify_init(void);
void
spotify_deinit(void);
int
spotify_login(const char *username, const char *password, const char **errmsg);
int
spotify_login_token(const char *username, const char *token, const char **errmsg);
void
spotify_logout(void);
int
spotify_relogin(void);
void
spotify_uri_register(const char *uri);
void
spotify_status_get(struct spotify_status *status);
#endif /* !__SPOTIFY_H__ */

View File

@ -0,0 +1,756 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
# include <pthread_np.h>
#endif
#include <event2/event.h>
#include "input.h"
#include "misc.h"
#include "logger.h"
#include "conffile.h"
#include "listener.h"
#include "http.h"
#include "db.h"
#include "transcode.h"
#include "spotify.h"
#include "librespot-c/librespot-c.h"
// Haven't actually studied ffmpeg's probe size requirements, this is just a
// guess
#define SPOTIFY_PROBE_SIZE_MIN 16384
// The transcoder will say EOF if too little data is provided to it
#define SPOTIFY_BUF_MIN 4096
// Limits how much of the Spotify Ogg file we fetch and buffer (in read_buf).
// This will also in effect throttle in librespot-c.
#define SPOTIFY_BUF_MAX (512 * 1024)
struct global_ctx
{
pthread_mutex_t lock;
pthread_cond_t cond;
struct spotify_status status;
struct sp_session *session;
enum sp_bitrates bitrate_preferred;
};
struct download_ctx
{
bool is_started;
bool is_ended;
struct transcode_ctx *xcode;
struct evbuffer *read_buf;
int read_fd;
uint32_t len_ms;
size_t len_bytes;
};
static struct global_ctx spotify_ctx;
static struct media_quality spotify_quality = { 44100, 16, 2, 0 };
/* ------------------------------ Utility funcs ----------------------------- */
static void
hextobin(uint8_t *data, size_t data_len, const char *hexstr, size_t hexstr_len)
{
char hex[] = { 0, 0, 0 };
const char *ptr;
int i;
if (2 * data_len < hexstr_len)
{
memset(data, 0, data_len);
return;
}
ptr = hexstr;
for (i = 0; i < data_len; i++, ptr+=2)
{
memcpy(hex, ptr, 2);
data[i] = strtol(hex, NULL, 16);
}
}
static int
postlogin(struct global_ctx *ctx)
{
struct sp_credentials credentials;
char *db_stored_cred;
char *ptr;
int i;
int ret;
ret = librespotc_credentials_get(&credentials, ctx->session);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Error getting Spotify credentials: %s\n", librespotc_last_errmsg());
return -1;
}
CHECK_NULL(L_SPOTIFY, db_stored_cred = malloc(2 * credentials.stored_cred_len + 1));
for (i = 0, ptr = db_stored_cred; i < credentials.stored_cred_len; i++)
ptr += sprintf(ptr, "%02x", credentials.stored_cred[i]);
db_admin_set("spotify_username", credentials.username);
db_admin_set("spotify_stored_cred", db_stored_cred);
free(db_stored_cred);
ctx->status.logged_in = true;
snprintf(ctx->status.username, sizeof(ctx->status.username), "%s", credentials.username);
librespotc_bitrate_set(ctx->session, ctx->bitrate_preferred);
DPRINTF(E_LOG, L_SPOTIFY, "Logged into Spotify succesfully with username %s\n", credentials.username);
listener_notify(LISTENER_SPOTIFY);
return 0;
}
// If there is evbuf size is below max, reads from a non-blocking fd until error,
// EAGAIN or evbuf full
static int
fd_read(bool *eofptr, struct evbuffer *evbuf, int fd)
{
size_t len = evbuffer_get_length(evbuf);
bool eof = false;
int total = 0;
int ret = 0;
while (len + total < SPOTIFY_BUF_MAX && !eof)
{
ret = evbuffer_read(evbuf, fd, -1); // Each read is 4096 bytes (EVBUFFER_READ_MAX)
if (ret == 0)
eof = true;
else if (ret < 0)
break;
total += ret;
}
if (eofptr)
*eofptr = eof;
if (ret < 0 && errno != EAGAIN)
return ret;
return total;
}
/* ------------------ Callbacks from librespot-c thread --------------------- */
static void
progress_cb(int fd, void *cb_arg, size_t received, size_t len)
{
DPRINTF(E_SPAM, L_SPOTIFY, "Progress %zu/%zu\n", received, len);
}
static int
https_get_cb(char **out, const char *url)
{
struct http_client_ctx ctx = { 0 };
char *body;
size_t len;
int ret;
ctx.url = url;
ctx.input_body = evbuffer_new();
ret = http_client_request(&ctx);
if (ret < 0 || ctx.response_code != HTTP_OK)
{
DPRINTF(E_LOG, L_SPOTIFY, "Failed to AP list from '%s' (return %d, error code %d)\n", ctx.url, ret, ctx.response_code);
goto error;
}
len = evbuffer_get_length(ctx.input_body);
body = malloc(len + 1);
evbuffer_remove(ctx.input_body, body, len);
body[len] = '\0'; // For safety
*out = body;
evbuffer_free(ctx.input_body);
return 0;
error:
evbuffer_free(ctx.input_body);
return -1;
}
static int
tcp_connect(const char *address, unsigned short port)
{
return net_connect(address, port, SOCK_STREAM, "spotify");
}
static void
tcp_disconnect(int fd)
{
close(fd);
}
static void
thread_name_set(pthread_t thread)
{
#if defined(HAVE_PTHREAD_SETNAME_NP)
pthread_setname_np(thread, "spotify");
#elif defined(HAVE_PTHREAD_SET_NAME_NP)
pthread_set_name_np(thread, "spotify");
#endif
}
static void
logmsg_cb(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
DVPRINTF(E_DBG, L_SPOTIFY, fmt, ap);
va_end(ap);
}
static void
hexdump_cb(const char *msg, uint8_t *data, size_t data_len)
{
// DHEXDUMP(E_DBG, L_SPOTIFY, data, data_len, msg);
}
/* --------------------- Implementation (input thread) ---------------------- */
struct sp_callbacks callbacks = {
.https_get = https_get_cb,
.tcp_connect = tcp_connect,
.tcp_disconnect = tcp_disconnect,
.thread_name_set = thread_name_set,
.hexdump = hexdump_cb,
.logmsg = logmsg_cb,
};
static int64_t
download_seek(void *arg, int64_t offset, enum transcode_seek_type type)
{
struct global_ctx *ctx = &spotify_ctx;
struct download_ctx *download = arg;
int64_t out;
int ret;
pthread_mutex_lock(&ctx->lock);
switch (type)
{
case XCODE_SEEK_SIZE:
out = download->len_bytes;
break;
case XCODE_SEEK_SET:
// Flush read buffer
evbuffer_drain(download->read_buf, -1);
ret = librespotc_seek(download->read_fd, offset);
if (ret < 0)
goto error;
fd_read(NULL, download->read_buf, download->read_fd);
out = offset;
break;
default:
goto error;
}
pthread_mutex_unlock(&ctx->lock);
DPRINTF(E_DBG, L_SPOTIFY, "Seek to offset %" PRIi64 " requested, type %d, returning %" PRIi64 "\n", offset, type, out);
return out;
error:
DPRINTF(E_WARN, L_SPOTIFY, "Seek error\n");
pthread_mutex_unlock(&ctx->lock);
return -1;
}
// Has to be called after we have started receiving data, since ffmpeg needs to
// probe the data to find the audio streams
static int
download_xcode_setup(struct download_ctx *download)
{
struct transcode_ctx *xcode;
struct transcode_evbuf_io xcode_evbuf_io = { 0 };
CHECK_NULL(L_SPOTIFY, xcode = malloc(sizeof(struct transcode_ctx)));
xcode_evbuf_io.evbuf = download->read_buf;
xcode_evbuf_io.seekfn = download_seek;
xcode_evbuf_io.seekfn_arg = download;
xcode->decode_ctx = transcode_decode_setup(XCODE_OGG, NULL, DATA_KIND_SPOTIFY, NULL, &xcode_evbuf_io, download->len_ms);
if (!xcode->decode_ctx)
goto error;
xcode->encode_ctx = transcode_encode_setup(XCODE_PCM16, NULL, xcode->decode_ctx, NULL, 0, 0);
if (!xcode->encode_ctx)
goto error;
download->xcode = xcode;
return 0;
error:
transcode_cleanup(&xcode);
return -1;
}
static void
download_free(struct download_ctx *download)
{
if (!download)
return;
if (download->read_fd >= 0)
librespotc_close(download->read_fd);
if (download->read_buf)
evbuffer_free(download->read_buf);
transcode_cleanup(&download->xcode);
free(download);
}
static struct download_ctx *
download_new(int fd, uint32_t len_ms, size_t len_bytes)
{
struct download_ctx *download;
CHECK_NULL(L_SPOTIFY, download = calloc(1, sizeof(struct download_ctx)));
CHECK_NULL(L_SPOTIFY, download->read_buf = evbuffer_new());
download->read_fd = fd;
download->len_ms = len_ms;
download->len_bytes = len_bytes;
return download;
}
static int
stop(struct input_source *source)
{
struct global_ctx *ctx = &spotify_ctx;
struct download_ctx *download = source->input_ctx;
DPRINTF(E_DBG, L_SPOTIFY, "stop()\n");
pthread_mutex_lock(&ctx->lock);
download_free(download);
if (source->evbuf)
evbuffer_free(source->evbuf);
source->input_ctx = NULL;
source->evbuf = NULL;
pthread_mutex_unlock(&ctx->lock);
return 0;
}
static int
setup(struct input_source *source)
{
struct global_ctx *ctx = &spotify_ctx;
struct download_ctx *download;
struct sp_metadata metadata;
int probe_bytes;
int fd;
int ret;
DPRINTF(E_DBG, L_SPOTIFY, "setup()\n");
pthread_mutex_lock(&ctx->lock);
fd = librespotc_open(source->path, ctx->session);
if (fd < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Eror opening source: %s\n", librespotc_last_errmsg());
goto error;
}
ret = librespotc_metadata_get(&metadata, fd);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Error getting track metadata: %s\n", librespotc_last_errmsg());
goto error;
}
// Seems we have a valid source, now setup a read + decoding context. The
// closing of the fd is from now on part of closing the download_ctx, which is
// done in stop().
download = download_new(fd, source->len_ms, metadata.file_len);
CHECK_NULL(L_SPOTIFY, source->evbuf = evbuffer_new());
CHECK_NULL(L_SPOTIFY, source->input_ctx = download);
source->quality = spotify_quality;
// At this point enough bytes should be ready for transcode setup (ffmpeg probing)
probe_bytes = fd_read(NULL, download->read_buf, fd);
if (probe_bytes < SPOTIFY_PROBE_SIZE_MIN)
{
DPRINTF(E_LOG, L_SPOTIFY, "Not enough audio data for ffmpeg probing (%d)\n", probe_bytes);
goto error;
}
ret = download_xcode_setup(download);
if (ret < 0)
goto error;
pthread_mutex_unlock(&ctx->lock);
return 0;
error:
pthread_mutex_unlock(&ctx->lock);
stop(source);
return -1;
}
static int
play(struct input_source *source)
{
struct download_ctx *download = source->input_ctx;
size_t buflen;
int ret;
// Starts the download. We don't do that in setup because the player/input
// might run seek() before starting download.
if (!download->is_started)
{
librespotc_write(download->read_fd, progress_cb, download);
download->is_started = true;
}
if (!download->is_ended)
{
ret = fd_read(&download->is_ended, download->read_buf, download->read_fd);
if (ret < 0)
goto error;
buflen = evbuffer_get_length(download->read_buf);
if (buflen < SPOTIFY_BUF_MIN)
goto wait;
}
// Decode the Ogg Vorbis to PCM in chunks of 16 packets, which is pretty much
// a randomly chosen chunk size
ret = transcode(source->evbuf, NULL, download->xcode, 16);
if (ret == 0)
{
input_write(source->evbuf, &source->quality, INPUT_FLAG_EOF);
stop(source);
return -1;
}
else if (ret < 0)
goto error;
ret = input_write(source->evbuf, &source->quality, 0);
if (ret == EAGAIN)
goto wait;
return 0;
error:
input_write(NULL, NULL, INPUT_FLAG_ERROR);
stop(source);
return -1;
wait:
DPRINTF(E_DBG, L_SPOTIFY, "Waiting for data\n");
input_wait();
return 0;
}
static int
seek(struct input_source *source, int seek_ms)
{
struct download_ctx *download = source->input_ctx;
// This will make transcode call back to download_seek(), but with a byte
// offset instead of a ms position, which is what librespot-c requires
return transcode_seek(download->xcode, seek_ms);
}
static int
login_stored_cred(struct global_ctx *ctx, const char *username, const char *db_stored_cred)
{
size_t db_stored_cred_len;
uint8_t *stored_cred = NULL;
size_t stored_cred_len;
int ret;
db_stored_cred_len = strlen(db_stored_cred);
stored_cred_len = db_stored_cred_len / 2;
CHECK_NULL(L_SPOTIFY, stored_cred = malloc(stored_cred_len));
hextobin(stored_cred, stored_cred_len, db_stored_cred, db_stored_cred_len);
ctx->session = librespotc_login_stored_cred(username, stored_cred, stored_cred_len);
if (!ctx->session)
{
DPRINTF(E_LOG, L_SPOTIFY, "Error logging into Spotify: %s\n", librespotc_last_errmsg());
goto error;
}
ret = postlogin(ctx);
if (ret < 0)
goto error;
free(stored_cred);
return 0;
error:
free(stored_cred);
if (ctx->session)
librespotc_logout(ctx->session);
ctx->session = NULL;
return -1;
}
static int
init(void)
{
struct sp_sysinfo sysinfo;
cfg_t *spotify_cfg;
char *username = NULL;
char *db_stored_cred = NULL;
int ret;
spotify_cfg = cfg_getsec(cfg, "spotify");
if (cfg_getbool(spotify_cfg, "use_libspotify"))
return -1;
CHECK_ERR(L_SPOTIFY, mutex_init(&spotify_ctx.lock));
CHECK_ERR(L_SPOTIFY, pthread_cond_init(&spotify_ctx.cond, NULL));
snprintf(sysinfo.client_name, sizeof(sysinfo.client_name), PACKAGE_NAME);
snprintf(sysinfo.client_version, sizeof(sysinfo.client_version), PACKAGE_VERSION);
snprintf(sysinfo.client_build_id, sizeof(sysinfo.client_build_id), "0");
snprintf(sysinfo.device_id, sizeof(sysinfo.device_id), "%" PRIx64, libhash); // TODO use a UUID instead
ret = librespotc_init(&sysinfo, &callbacks);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Error initializing Spotify: %s\n", librespotc_last_errmsg());
goto error;
}
switch (cfg_getint(spotify_cfg, "bitrate"))
{
case 1:
spotify_ctx.bitrate_preferred = SP_BITRATE_96;
break;
case 2:
spotify_ctx.bitrate_preferred = SP_BITRATE_160;
break;
case 3:
spotify_ctx.bitrate_preferred = SP_BITRATE_320;
break;
default:
spotify_ctx.bitrate_preferred = SP_BITRATE_ANY;
}
// Re-login if we have stored credentials
db_admin_get(&username, "spotify_username");
db_admin_get(&db_stored_cred, "spotify_stored_cred");
if (username && db_stored_cred)
{
ret = login_stored_cred(&spotify_ctx, username, db_stored_cred);
if (ret < 0)
goto error;
}
free(username);
free(db_stored_cred);
return 0;
error:
free(username);
free(db_stored_cred);
return -1;
}
static void
deinit(void)
{
librespotc_deinit();
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&spotify_ctx.cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&spotify_ctx.lock));
}
struct input_definition input_spotify =
{
.name = "Spotify",
.type = INPUT_TYPE_SPOTIFY,
.disabled = 0,
.setup = setup,
.stop = stop,
.play = play,
.seek = seek,
.init = init,
.deinit = deinit,
};
/* -------------------- Functions exposed via spotify.h --------------------- */
/* Called from other threads than the input thread */
static int
login(const char *username, const char *password, const char **errmsg)
{
struct global_ctx *ctx = &spotify_ctx;
int ret;
pthread_mutex_lock(&ctx->lock);
ctx->session = librespotc_login_password(username, password);
if (!ctx->session)
goto error;
ret = postlogin(ctx);
if (ret < 0)
goto error;
pthread_mutex_unlock(&ctx->lock);
return 0;
error:
if (ctx->session)
librespotc_logout(ctx->session);
ctx->session = NULL;
if (errmsg)
*errmsg = librespotc_last_errmsg();
pthread_mutex_unlock(&ctx->lock);
return -1;
}
static int
login_token(const char *username, const char *token, const char **errmsg)
{
struct global_ctx *ctx = &spotify_ctx;
int ret;
pthread_mutex_lock(&ctx->lock);
ctx->session = librespotc_login_token(username, token);
if (!ctx->session)
goto error;
ret = postlogin(ctx);
if (ret < 0)
goto error;
pthread_mutex_unlock(&ctx->lock);
return 0;
error:
if (ctx->session)
librespotc_logout(ctx->session);
ctx->session = NULL;
if (errmsg)
*errmsg = librespotc_last_errmsg();
pthread_mutex_unlock(&ctx->lock);
return -1;
}
static void
logout(void)
{
struct global_ctx *ctx = &spotify_ctx;
db_admin_delete("spotify_username");
db_admin_delete("spotify_stored_cred");
pthread_mutex_lock(&ctx->lock);
librespotc_logout(ctx->session);
ctx->session = NULL;
pthread_mutex_unlock(&ctx->lock);
}
static int
relogin(void)
{
return 0; // re-login is only relevant for libspotify, here it is just a no-op
}
static void
status_get(struct spotify_status *status)
{
struct global_ctx *ctx = &spotify_ctx;
pthread_mutex_lock(&ctx->lock);
memcpy(status->username, ctx->status.username, sizeof(status->username));
status->logged_in = ctx->status.logged_in;
status->installed = true;
pthread_mutex_unlock(&ctx->lock);
}
struct spotify_backend spotify_librespotc =
{
.login = login,
.login_token = login_token,
.logout = logout,
.relogin = relogin,
.status_get = status_get,
};

View File

@ -0,0 +1,133 @@
/*
* Copyright (C) 2017 Espen Jurgensen
*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include "input.h"
#include "conffile.h"
#include "logger.h"
#include "spotify.h"
#include "libspotify/libspotify.h"
// How many retries to start playback if resource is still loading
#define LIBSPOTIFY_SETUP_RETRIES 5
// How long to wait between retries in microseconds (500000 = 0.5 seconds)
#define LIBSPOTIFY_SETUP_RETRY_WAIT 500000
static int
init(void)
{
return cfg_getbool(cfg_getsec(cfg, "spotify"), "use_libspotify") ? 0 : -1;
}
static int
setup(struct input_source *source)
{
int i = 0;
int ret;
while((ret = libspotify_playback_setup(source->path)) == LIBSPOTIFY_SETUP_ERROR_IS_LOADING)
{
if (i >= LIBSPOTIFY_SETUP_RETRIES)
break;
DPRINTF(E_DBG, L_SPOTIFY, "Resource still loading (%d)\n", i);
usleep(LIBSPOTIFY_SETUP_RETRY_WAIT);
i++;
}
if (ret < 0)
return -1;
ret = libspotify_playback_play();
if (ret < 0)
return -1;
return 0;
}
static int
stop(struct input_source *source)
{
int ret;
ret = libspotify_playback_stop();
if (ret < 0)
return -1;
return 0;
}
static int
seek(struct input_source *source, int seek_ms)
{
int ret;
ret = libspotify_playback_seek(seek_ms);
if (ret < 0)
return -1;
return ret;
}
struct input_definition input_libspotify =
{
.name = "libspotify",
.type = INPUT_TYPE_LIBSPOTIFY,
.disabled = 0,
.init = init,
.setup = setup,
.stop = stop,
.seek = seek,
};
// No-op for libspotify since it doesn't support logging in with the web api token
static int
login_token(const char *username, const char *token, const char **errmsg)
{
return 0;
}
static void
status_get(struct spotify_status *status)
{
struct spotify_status_info info = { 0 };
libspotify_status_info_get(&info);
status->installed = info.libspotify_installed;
status->logged_in = info.libspotify_logged_in;
snprintf(status->username, sizeof(status->username), "%s", info.libspotify_user);
}
struct spotify_backend spotify_libspotify =
{
.init = libspotify_init,
.deinit = libspotify_deinit,
.login = libspotify_login,
.login_token = login_token,
.logout = libspotify_logout,
.relogin = libspotify_relogin,
.uri_register = libspotify_uri_register,
.status_get = status_get,
};

View File

@ -79,14 +79,14 @@ static pthread_t tid_library;
struct event_base *evbase_lib;
extern struct library_source filescanner;
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY
extern struct library_source spotifyscanner;
#endif
extern struct library_source rssscanner;
static struct library_source *sources[] = {
&filescanner,
#ifdef HAVE_SPOTIFY_H
#ifdef SPOTIFY
&spotifyscanner,
#endif
&rssscanner,

View File

@ -67,9 +67,6 @@
#ifdef LASTFM
# include "lastfm.h"
#endif
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif
#define F_SCAN_BULK (1 << 0)
@ -95,7 +92,6 @@ enum file_type {
FILE_CTRL_REMOTE,
FILE_CTRL_RAOP_VERIFICATION,
FILE_CTRL_LASTFM,
FILE_CTRL_SPOTIFY,
FILE_CTRL_INITSCAN,
FILE_CTRL_METASCAN, // forced scan for meta, preserves existing db records
FILE_CTRL_FULLSCAN,
@ -350,9 +346,6 @@ file_type_get(const char *path) {
if (strcasecmp(ext, ".lastfm") == 0)
return FILE_CTRL_LASTFM;
if (strcasecmp(ext, ".spotify") == 0)
return FILE_CTRL_SPOTIFY;
if (strcasecmp(ext, ".init-rescan") == 0)
return FILE_CTRL_INITSCAN;
@ -700,17 +693,6 @@ process_file(char *file, struct stat *sb, enum file_type file_type, int scan_typ
#endif
break;
case FILE_CTRL_SPOTIFY:
#ifdef HAVE_SPOTIFY_H
if (flags & F_SCAN_BULK)
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
else
kickoff(spotify_login, file, 2);
#else
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without Spotify support\n", file);
#endif
break;
case FILE_CTRL_INITSCAN:
if (flags & F_SCAN_BULK)
break;

View File

@ -35,7 +35,7 @@
#include "listener.h"
#include "logger.h"
#include "misc_json.h"
#include "spotify.h"
#include "inputs/spotify.h"
enum spotify_request_type {
@ -135,7 +135,7 @@ static bool scanning;
// Endpoints and credentials for the web api
static const char *spotify_client_id = "0e684a5422384114a8ae7ac020f01789";
static const char *spotify_client_secret = "232af95f39014c9ba218285a5c11a239";
static const char *spotify_scope = "playlist-read-private playlist-read-collaborative user-library-read user-read-private";
static const char *spotify_scope = "playlist-read-private playlist-read-collaborative user-library-read user-read-private streaming";
static const char *spotify_auth_uri = "https://accounts.spotify.com/authorize";
static const char *spotify_token_uri = "https://accounts.spotify.com/api/token";
@ -975,10 +975,9 @@ spotifywebapi_oauth_uri_get(const char *redirect_uri)
/* Thread: httpd */
int
spotifywebapi_oauth_callback(struct evkeyvalq *param, const char *redirect_uri, char **errmsg)
spotifywebapi_oauth_callback(struct evkeyvalq *param, const char *redirect_uri, const char **errmsg)
{
const char *code;
const char *err;
int ret;
*errmsg = NULL;
@ -986,18 +985,19 @@ spotifywebapi_oauth_callback(struct evkeyvalq *param, const char *redirect_uri,
code = evhttp_find_header(param, "code");
if (!code)
{
*errmsg = safe_asprintf("Error: Didn't receive a code from Spotify");
*errmsg = "Error: Didn't receive a code from Spotify";
return -1;
}
DPRINTF(E_DBG, L_SPOTIFY, "Received OAuth code: %s\n", code);
ret = token_get(code, redirect_uri, &err);
ret = token_get(code, redirect_uri, errmsg);
if (ret < 0)
{
*errmsg = safe_asprintf("Error: %s", err);
return -1;
}
return -1;
ret = spotify_login_token(spotify_credentials.user, spotify_credentials.access_token, errmsg);
if (ret < 0)
return -1;
// Trigger scan after successful access to spotifywebapi
spotifywebapi_fullrescan();
@ -1471,6 +1471,7 @@ track_add(struct spotify_track *track, struct spotify_album *album, const char *
free_mfi(&mfi, 1);
}
// This is only required for the libspotify backend
spotify_uri_register(track->uri);
if (album && album->uri)
@ -1712,7 +1713,7 @@ scan_playlists(enum spotify_request_type request_type)
}
static void
create_saved_tracks_playlist()
create_saved_tracks_playlist(void)
{
struct playlist_info pli =
{
@ -1738,7 +1739,7 @@ create_saved_tracks_playlist()
* Add or update playlist folder for all spotify playlists (if enabled in config)
*/
static void
create_base_playlist()
create_base_playlist(void)
{
cfg_t *spotify_cfg;
struct playlist_info pli =
@ -1795,17 +1796,17 @@ scan(enum spotify_request_type request_type)
/* Thread: library */
static int
initscan()
initscan(void)
{
int ret;
/* Refresh access token for the spotify webapi */
ret = token_refresh();
ret = token_refresh();
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Spotify webapi token refresh failed. "
"In order to use the web api, authorize the server to access "
"your saved tracks by visiting http://owntone.local:3689\n");
"In order to use Spotify, authorize the server to access your saved "
"tracks by visiting http://owntone.local:3689\n");
db_spotify_purge();
@ -1815,8 +1816,8 @@ initscan()
spotify_saved_plid = 0;
/*
* Login to spotify needs to be done before scanning tracks from the web api.
* (Scanned tracks need to be registered with libspotify for playback)
* libspotify needs to be logged in before before scanning tracks from the web
* since scanned tracks need to be registered for playback
*/
ret = spotify_relogin();
if (ret < 0)
@ -1839,7 +1840,7 @@ initscan()
/* Thread: library */
static int
rescan()
rescan(void)
{
scan(SPOTIFY_REQUEST_TYPE_RESCAN);
return 0;
@ -1847,7 +1848,7 @@ rescan()
/* Thread: library */
static int
metarescan()
metarescan(void)
{
scan(SPOTIFY_REQUEST_TYPE_METARESCAN);
return 0;
@ -1855,7 +1856,7 @@ metarescan()
/* Thread: library */
static int
fullrescan()
fullrescan(void)
{
db_spotify_purge();
scan(SPOTIFY_REQUEST_TYPE_RESCAN);
@ -2066,12 +2067,10 @@ spotifywebapi_access_token_get(struct spotifywebapi_access_token *info)
static int
spotifywebapi_init()
{
int ret;
CHECK_ERR(L_SPOTIFY, mutex_init(&token_lck));
ret = spotify_init();
return ret;
// Required for libspotify backend
return spotify_init();
}
static void

View File

@ -45,7 +45,7 @@ struct spotifywebapi_access_token
char *
spotifywebapi_oauth_uri_get(const char *redirect_uri);
int
spotifywebapi_oauth_callback(struct evkeyvalq *param, const char *redirect_uri, char **errmsg);
spotifywebapi_oauth_callback(struct evkeyvalq *param, const char *redirect_uri, const char **errmsg);
void
spotifywebapi_fullrescan(void);

View File

@ -70,6 +70,12 @@ static char *buildopts[] =
#else
"Without Spotify",
#endif
#ifdef SPOTIFY_LIBRESPOTC
"librespot-c",
#endif
#ifdef SPOTIFY_LIBSPOTIFY
"libspotify",
#endif
#ifdef LASTFM
"LastFM",
#else

View File

@ -1,68 +0,0 @@
#ifndef __SPOTIFY_H__
#define __SPOTIFY_H__
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/http.h>
#include <stdbool.h>
struct spotify_status_info
{
bool libspotify_installed;
bool libspotify_logged_in;
char libspotify_user[100];
};
#define SPOTIFY_SETUP_ERROR_IS_LOADING -2
int
spotify_playback_setup(const char *path);
int
spotify_playback_play();
int
spotify_playback_pause();
void
spotify_playback_pause_nonblock(void);
int
spotify_playback_stop(void);
void
spotify_playback_stop_nonblock(void);
int
spotify_playback_seek(int ms);
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);
int
spotify_relogin();
int
spotify_login_user(const char *user, const char *password, char **errmsg);
void
spotify_login(char **arglist);
void
spotify_logout(void);
void
spotify_status_info_get(struct spotify_status_info *info);
void
spotify_uri_register(const char *uri);
int
spotify_init(void);
void
spotify_deinit(void);
#endif /* !__SPOTIFY_H__ */

View File

@ -39,7 +39,6 @@
#include "logger.h"
#include "conffile.h"
#include "db.h"
#include "avio_evbuffer.h"
#include "misc.h"
#include "transcode.h"
@ -51,6 +50,8 @@
#define MAX_BAD_PACKETS 5
// How long to wait (in microsec) before interrupting av_read_frame
#define READ_TIMEOUT 30000000
// Buffer size for reading/writing input and output evbuffers
#define AVIO_BUFFER_SIZE 4096
static const char *default_codecs = "mpeg,wav";
static const char *roku_codecs = "mpeg,mp4a,wma,alac,wav";
@ -184,6 +185,14 @@ enum probe_type
PROBE_TYPE_QUICK,
};
struct avio_evbuffer {
struct evbuffer *evbuf;
uint8_t *buffer;
transcode_seekfn seekfn;
void *seekfn_arg;
};
/* -------------------------- PROFILE CONFIGURATION ------------------------ */
static int
@ -242,6 +251,11 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile, str
settings->sample_format = AV_SAMPLE_FMT_S16P;
break;
case XCODE_OGG:
settings->encode_audio = 1;
settings->in_format = "ogg";
break;
case XCODE_JPEG:
settings->encode_video = 1;
settings->silent = 1;
@ -771,6 +785,131 @@ read_decode_filter_encode_write(struct transcode_ctx *ctx)
return ret;
}
/* ------------------------------- CUSTOM I/O ------------------------------ */
/* For using ffmpeg with evbuffer input/output instead of files */
static int
avio_evbuffer_read(void *opaque, uint8_t *buf, int size)
{
struct avio_evbuffer *ae = (struct avio_evbuffer *)opaque;
int ret;
ret = evbuffer_remove(ae->evbuf, buf, size);
// Must return AVERROR, see avio.h: avio_alloc_context()
return (ret > 0) ? ret : AVERROR_EOF;
}
static int
avio_evbuffer_write(void *opaque, uint8_t *buf, int size)
{
struct avio_evbuffer *ae = (struct avio_evbuffer *)opaque;
int ret;
ret = evbuffer_add(ae->evbuf, buf, size);
return (ret == 0) ? size : -1;
}
static int64_t
avio_evbuffer_seek(void *opaque, int64_t offset, int whence)
{
struct avio_evbuffer *ae = (struct avio_evbuffer *)opaque;
enum transcode_seek_type seek_type;
// Caller shouldn't need to know about ffmpeg defines
if (whence & AVSEEK_SIZE)
seek_type = XCODE_SEEK_SIZE;
else if (whence == SEEK_SET)
seek_type = XCODE_SEEK_SET;
else if (whence == SEEK_CUR)
seek_type = XCODE_SEEK_CUR;
else
return -1;
return ae->seekfn(ae->seekfn_arg, offset, seek_type);
}
static AVIOContext *
avio_evbuffer_open(struct transcode_evbuf_io *evbuf_io, int is_output)
{
struct avio_evbuffer *ae;
AVIOContext *s;
ae = calloc(1, sizeof(struct avio_evbuffer));
if (!ae)
{
DPRINTF(E_LOG, L_FFMPEG, "Out of memory for avio_evbuffer\n");
return NULL;
}
ae->buffer = av_mallocz(AVIO_BUFFER_SIZE);
if (!ae->buffer)
{
DPRINTF(E_LOG, L_FFMPEG, "Out of memory for avio buffer\n");
free(ae);
return NULL;
}
ae->evbuf = evbuf_io->evbuf;
ae->seekfn = evbuf_io->seekfn;
ae->seekfn_arg = evbuf_io->seekfn_arg;
if (is_output)
s = avio_alloc_context(ae->buffer, AVIO_BUFFER_SIZE, 1, ae, NULL, avio_evbuffer_write, NULL);
else
s = avio_alloc_context(ae->buffer, AVIO_BUFFER_SIZE, 0, ae, avio_evbuffer_read, NULL, (evbuf_io->seekfn ? avio_evbuffer_seek : NULL));
if (!s)
{
DPRINTF(E_LOG, L_FFMPEG, "Could not allocate AVIOContext\n");
av_free(ae->buffer);
free(ae);
return NULL;
}
s->seekable = (evbuf_io->seekfn ? AVIO_SEEKABLE_NORMAL : 0);
return s;
}
static AVIOContext *
avio_input_evbuffer_open(struct transcode_evbuf_io *evbuf_io)
{
return avio_evbuffer_open(evbuf_io, 0);
}
static AVIOContext *
avio_output_evbuffer_open(struct evbuffer *evbuf)
{
struct transcode_evbuf_io evbuf_io = { 0 };
evbuf_io.evbuf = evbuf;
return avio_evbuffer_open(&evbuf_io, 1);
}
static void
avio_evbuffer_close(AVIOContext *s)
{
struct avio_evbuffer *ae;
if (!s)
return;
ae = (struct avio_evbuffer *)s->opaque;
avio_flush(s);
av_free(s->buffer);
free(ae);
av_free(s);
}
/* --------------------------- INPUT/OUTPUT INIT --------------------------- */
@ -821,7 +960,7 @@ open_decoder(AVCodecContext **dec_ctx, unsigned int *stream_index, struct decode
}
static int
open_input(struct decode_ctx *ctx, const char *path, struct evbuffer *evbuf, enum probe_type probe_type)
open_input(struct decode_ctx *ctx, const char *path, struct transcode_evbuf_io *evbuf_io, enum probe_type probe_type)
{
AVDictionary *options = NULL;
AVCodecContext *dec_ctx;
@ -861,7 +1000,7 @@ open_input(struct decode_ctx *ctx, const char *path, struct evbuffer *evbuf, enu
ctx->ifmt_ctx->interrupt_callback.opaque = ctx;
ctx->timestamp = av_gettime();
if (evbuf)
if (evbuf_io)
{
ifmt = av_find_input_format(ctx->settings.in_format);
if (!ifmt)
@ -870,7 +1009,7 @@ open_input(struct decode_ctx *ctx, const char *path, struct evbuffer *evbuf, enu
goto out_fail;
}
CHECK_NULL(L_XCODE, ctx->avio = avio_input_evbuffer_open(evbuf));
CHECK_NULL(L_XCODE, ctx->avio = avio_input_evbuffer_open(evbuf_io));
ctx->ifmt_ctx->pb = ctx->avio;
ret = avformat_open_input(&ctx->ifmt_ctx, NULL, ifmt, &options);
@ -1232,7 +1371,7 @@ close_filters(struct encode_ctx *ctx)
/* Setup */
struct decode_ctx *
transcode_decode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length)
transcode_decode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, struct transcode_evbuf_io *evbuf_io, uint32_t song_length)
{
struct decode_ctx *ctx;
int ret;
@ -1250,14 +1389,14 @@ transcode_decode_setup(enum transcode_profile profile, struct media_quality *qua
if (data_kind == DATA_KIND_HTTP)
{
ret = open_input(ctx, path, evbuf, PROBE_TYPE_QUICK);
ret = open_input(ctx, path, evbuf_io, PROBE_TYPE_QUICK);
// Retry with a default, slower probe size
if (ret == AVERROR_STREAM_NOT_FOUND)
ret = open_input(ctx, path, evbuf, PROBE_TYPE_DEFAULT);
ret = open_input(ctx, path, evbuf_io, PROBE_TYPE_DEFAULT);
}
else
ret = open_input(ctx, path, evbuf, PROBE_TYPE_DEFAULT);
ret = open_input(ctx, path, evbuf_io, PROBE_TYPE_DEFAULT);
if (ret < 0)
goto fail_free;

View File

@ -19,18 +19,30 @@ enum transcode_profile
XCODE_PCM16,
XCODE_PCM24,
XCODE_PCM32,
// Transcodes the best audio stream into MP3
// Transcodes the best audio stream to MP3
XCODE_MP3,
// Transcodes the best audio stream into OPUS
// Transcodes the best audio stream to OPUS
XCODE_OPUS,
// Transcodes the best audio stream into ALAC
// Transcodes the best audio stream to ALAC
XCODE_ALAC,
// Transcodes the best video stream into JPEG/PNG/VP8
// Transcodes the best audio stream from OGG
XCODE_OGG,
// Transcodes the best video stream to JPEG/PNG/VP8
XCODE_JPEG,
XCODE_PNG,
XCODE_VP8,
};
enum transcode_seek_type
{
XCODE_SEEK_SIZE,
XCODE_SEEK_SET,
XCODE_SEEK_CUR,
};
typedef void transcode_frame;
typedef int64_t(*transcode_seekfn)(void *arg, int64_t offset, enum transcode_seek_type seek_type);
struct decode_ctx;
struct encode_ctx;
struct transcode_ctx
@ -39,11 +51,18 @@ struct transcode_ctx
struct encode_ctx *encode_ctx;
};
typedef void transcode_frame;
struct transcode_evbuf_io
{
struct evbuffer *evbuf;
// Set to null if no seek support required
transcode_seekfn seekfn;
void *seekfn_arg;
};
// Setting up
struct decode_ctx *
transcode_decode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length);
transcode_decode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, struct transcode_evbuf_io *evbuf_io, uint32_t song_length);
struct encode_ctx *
transcode_encode_setup(enum transcode_profile profile, struct media_quality *quality, struct decode_ctx *src_ctx, off_t *est_size, int width, int height);