Merge branch 'spotifyc5'
This commit is contained in:
commit
a1f9e10f9c
32
INSTALL.md
32
INSTALL.md
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
55
README.md
55
README.md
|
@ -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
|
||||
|
||||
|
|
36
configure.ac
36
configure.ac
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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__ */
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
28
src/input.c
28
src/input.c
|
@ -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
|
||||
|
|
11
src/input.h
11
src/input.h
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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__ */
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Christian Meffert <christian.meffert@googlemail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "commands.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct command
|
||||
{
|
||||
pthread_mutex_t lck;
|
||||
pthread_cond_t cond;
|
||||
|
||||
command_function func;
|
||||
command_function func_bh;
|
||||
void *arg;
|
||||
int nonblock;
|
||||
int ret;
|
||||
int pending;
|
||||
};
|
||||
|
||||
struct commands_base
|
||||
{
|
||||
struct event_base *evbase;
|
||||
command_exit_cb exit_cb;
|
||||
int command_pipe[2];
|
||||
struct event *command_event;
|
||||
struct command *current_cmd;
|
||||
};
|
||||
|
||||
static int
|
||||
mutex_init(pthread_mutex_t *mutex)
|
||||
{
|
||||
pthread_mutexattr_t mattr;
|
||||
int err;
|
||||
|
||||
pthread_mutexattr_init(&mattr);
|
||||
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK);
|
||||
err = pthread_mutex_init(mutex, &mattr);
|
||||
pthread_mutexattr_destroy(&mattr);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Asynchronous execution of the command function
|
||||
*/
|
||||
static void
|
||||
command_cb_async(struct commands_base *cmdbase, struct command *cmd)
|
||||
{
|
||||
enum command_state cmdstate;
|
||||
|
||||
// Command is executed asynchronously
|
||||
cmdstate = cmd->func(cmd->arg, &cmd->ret);
|
||||
|
||||
// Only free arg if there are no pending events (used in worker.c)
|
||||
if (cmdstate != COMMAND_PENDING && cmd->arg)
|
||||
free(cmd->arg);
|
||||
|
||||
free(cmd);
|
||||
|
||||
event_add(cmdbase->command_event, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Synchronous execution of the command function
|
||||
*/
|
||||
static void
|
||||
command_cb_sync(struct commands_base *cmdbase, struct command *cmd)
|
||||
{
|
||||
enum command_state cmdstate;
|
||||
|
||||
pthread_mutex_lock(&cmd->lck);
|
||||
|
||||
cmdstate = cmd->func(cmd->arg, &cmd->ret);
|
||||
if (cmdstate == COMMAND_PENDING)
|
||||
{
|
||||
// Command execution is waiting for pending events before returning to the caller
|
||||
cmdbase->current_cmd = cmd;
|
||||
cmd->pending = cmd->ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command execution finished, execute the bottom half function
|
||||
if (cmd->ret == 0 && cmd->func_bh)
|
||||
cmd->func_bh(cmd->arg, &cmd->ret);
|
||||
|
||||
event_add(cmdbase->command_event, NULL);
|
||||
|
||||
// Signal the calling thread that the command execution finished
|
||||
pthread_cond_signal(&cmd->cond);
|
||||
pthread_mutex_unlock(&cmd->lck);
|
||||
|
||||
// Note if cmd->func was cmdloop_exit then cmdbase may be invalid now,
|
||||
// because commands_base_destroy() may have freed it
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Event callback function
|
||||
*
|
||||
* Function is triggered by libevent if there is data to read on the command pipe (writing to the command pipe happens through
|
||||
* the send_command function).
|
||||
*/
|
||||
static void
|
||||
command_cb(int fd, short what, void *arg)
|
||||
{
|
||||
struct commands_base *cmdbase;
|
||||
struct command *cmd;
|
||||
int ret;
|
||||
|
||||
cmdbase = arg;
|
||||
|
||||
// Get the command to execute from the pipe
|
||||
ret = read(cmdbase->command_pipe[0], &cmd, sizeof(cmd));
|
||||
if (ret != sizeof(cmd))
|
||||
{
|
||||
// Incorrect length, ignore
|
||||
event_add(cmdbase->command_event, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the command function
|
||||
if (cmd->nonblock)
|
||||
{
|
||||
// Command is executed asynchronously
|
||||
command_cb_async(cmdbase, cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command is executed synchronously, caller is waiting until signaled that the execution finished
|
||||
command_cb_sync(cmdbase, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes the given command to the command pipe
|
||||
*/
|
||||
static int
|
||||
send_command(struct commands_base *cmdbase, struct command *cmd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!cmd->func)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = write(cmdbase->command_pipe[1], &cmd, sizeof(cmd));
|
||||
if (ret != sizeof(cmd))
|
||||
{
|
||||
// errno set by write()
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees the command base and closes the (internally used) pipes
|
||||
*/
|
||||
int
|
||||
commands_base_free(struct commands_base *cmdbase)
|
||||
{
|
||||
if (cmdbase->command_event)
|
||||
event_free(cmdbase->command_event);
|
||||
|
||||
close(cmdbase->command_pipe[0]);
|
||||
close(cmdbase->command_pipe[1]);
|
||||
free(cmdbase);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a new command base, needs to be freed by commands_base_destroy or commands_base_free.
|
||||
*
|
||||
* @param evbase The libevent base to use for command handling
|
||||
* @param exit_cb Optional callback function to be called during commands_base_destroy
|
||||
*/
|
||||
struct commands_base *
|
||||
commands_base_new(struct event_base *evbase, command_exit_cb exit_cb)
|
||||
{
|
||||
struct commands_base *cmdbase;
|
||||
int ret;
|
||||
|
||||
cmdbase = calloc(1, sizeof(struct commands_base));
|
||||
|
||||
#ifdef HAVE_PIPE2
|
||||
ret = pipe2(cmdbase->command_pipe, O_CLOEXEC);
|
||||
#else
|
||||
ret = pipe(cmdbase->command_pipe);
|
||||
#endif
|
||||
if (ret < 0)
|
||||
{
|
||||
// errno set by pipe
|
||||
free(cmdbase);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cmdbase->command_event = event_new(evbase, cmdbase->command_pipe[0], EV_READ, command_cb, cmdbase);
|
||||
if (!cmdbase->command_event)
|
||||
{
|
||||
commands_base_free(cmdbase);
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = event_add(cmdbase->command_event, NULL);
|
||||
if (ret != 0)
|
||||
{
|
||||
commands_base_free(cmdbase);
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cmdbase->evbase = evbase;
|
||||
cmdbase->exit_cb = exit_cb;
|
||||
|
||||
return cmdbase;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the current return value for the current pending command.
|
||||
*
|
||||
* If a command has more than one pending event, each event can access the previous set return value
|
||||
* if it depends on it.
|
||||
*
|
||||
* @param cmdbase The command base
|
||||
* @return The current return value
|
||||
*/
|
||||
int
|
||||
commands_exec_returnvalue(struct commands_base *cmdbase)
|
||||
{
|
||||
if (cmdbase->current_cmd == NULL)
|
||||
return 0;
|
||||
|
||||
return cmdbase->current_cmd->ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a command function returned COMMAND_PENDING, each event triggered by this command needs to
|
||||
* call command_exec_end, passing it the return value of the event execution.
|
||||
*
|
||||
* If a command function is waiting for multiple events, each event needs to call command_exec_end.
|
||||
* The command base keeps track of the number of still pending events and only returns to the caller
|
||||
* if there are no pending events left.
|
||||
*
|
||||
* @param cmdbase The command base (holds the current pending command)
|
||||
* @param retvalue The return value for the calling thread
|
||||
*/
|
||||
void
|
||||
commands_exec_end(struct commands_base *cmdbase, int retvalue)
|
||||
{
|
||||
struct command *current_cmd = cmdbase->current_cmd;
|
||||
|
||||
if (!current_cmd)
|
||||
return;
|
||||
|
||||
// A pending event finished, decrease the number of pending events and update the return value
|
||||
current_cmd->pending--;
|
||||
current_cmd->ret = retvalue;
|
||||
|
||||
// If there are still pending events return
|
||||
if (current_cmd->pending > 0)
|
||||
return;
|
||||
|
||||
// All pending events have finished, execute the bottom half and signal the caller that the command execution finished
|
||||
if (current_cmd->func_bh)
|
||||
current_cmd->func_bh(current_cmd->arg, ¤t_cmd->ret);
|
||||
|
||||
cmdbase->current_cmd = NULL;
|
||||
|
||||
/* Process commands again */
|
||||
event_add(cmdbase->command_event, NULL);
|
||||
|
||||
pthread_cond_signal(¤t_cmd->cond);
|
||||
pthread_mutex_unlock(¤t_cmd->lck);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute the function 'func' with the given argument 'arg' in the event loop thread.
|
||||
* Blocks the caller (thread) until the function returned.
|
||||
*
|
||||
* If a function 'func_bh' ("bottom half") is given, it is executed after 'func' has successfully
|
||||
* finished.
|
||||
*
|
||||
* @param cmdbase The command base
|
||||
* @param func The function to be executed
|
||||
* @param func_bh The bottom half function to be executed after all pending events from func are processed
|
||||
* @param arg Argument passed to func (and func_bh)
|
||||
* @return Return value of func (or func_bh if func_bh is not NULL)
|
||||
*/
|
||||
int
|
||||
commands_exec_sync(struct commands_base *cmdbase, command_function func, command_function func_bh, void *arg)
|
||||
{
|
||||
struct command cmd;
|
||||
int errsv = 0;
|
||||
int ret;
|
||||
|
||||
memset(&cmd, 0, sizeof(struct command));
|
||||
cmd.func = func;
|
||||
cmd.func_bh = func_bh;
|
||||
cmd.arg = arg;
|
||||
cmd.nonblock = 0;
|
||||
|
||||
mutex_init(&cmd.lck);
|
||||
pthread_cond_init(&cmd.cond, NULL);
|
||||
|
||||
pthread_mutex_lock(&cmd.lck);
|
||||
|
||||
ret = send_command(cmdbase, &cmd);
|
||||
if (ret < 0)
|
||||
{
|
||||
errsv = errno;
|
||||
cmd.ret = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_cond_wait(&cmd.cond, &cmd.lck);
|
||||
}
|
||||
|
||||
// May change errno, but we don't care about that
|
||||
pthread_mutex_unlock(&cmd.lck);
|
||||
|
||||
pthread_cond_destroy(&cmd.cond);
|
||||
pthread_mutex_destroy(&cmd.lck);
|
||||
|
||||
errno = errsv;
|
||||
return cmd.ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute the function 'func' with the given argument 'arg' in the event loop thread.
|
||||
* Triggers the function execution and immediately returns (does not wait for func to finish).
|
||||
*
|
||||
* The pointer passed as argument is freed in the event loop thread after func returned.
|
||||
*
|
||||
* @param cmdbase The command base
|
||||
* @param func The function to be executed
|
||||
* @param arg Argument passed to func
|
||||
* @return 0 if triggering the function execution succeeded, -1 on failure.
|
||||
*/
|
||||
int
|
||||
commands_exec_async(struct commands_base *cmdbase, command_function func, void *arg)
|
||||
{
|
||||
struct command *cmd;
|
||||
int ret;
|
||||
|
||||
cmd = calloc(1, sizeof(struct command));
|
||||
cmd->func = func;
|
||||
cmd->func_bh = NULL;
|
||||
cmd->arg = arg;
|
||||
cmd->nonblock = 1;
|
||||
|
||||
ret = send_command(cmdbase, cmd);
|
||||
if (ret < 0)
|
||||
{
|
||||
free(cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Command to break the libevent loop
|
||||
*
|
||||
* If the command base was created with an exit_cb function, exit_cb is called before breaking the
|
||||
* libevent loop.
|
||||
*
|
||||
* @param arg The command base
|
||||
* @param retval Always set to COMMAND_END
|
||||
*/
|
||||
static enum command_state
|
||||
cmdloop_exit(void *arg, int *retval)
|
||||
{
|
||||
struct commands_base *cmdbase = arg;
|
||||
*retval = 0;
|
||||
|
||||
if (cmdbase->exit_cb)
|
||||
cmdbase->exit_cb();
|
||||
|
||||
event_base_loopbreak(cmdbase->evbase);
|
||||
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
/*
|
||||
* Break the libevent loop for the given command base, closes the internally used pipes
|
||||
* and frees the command base.
|
||||
*
|
||||
* @param cmdbase The command base
|
||||
*/
|
||||
void
|
||||
commands_base_destroy(struct commands_base *cmdbase)
|
||||
{
|
||||
commands_exec_sync(cmdbase, cmdloop_exit, NULL, cmdbase);
|
||||
commands_base_free(cmdbase);
|
||||
}
|
||||
|
|
@ -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
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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__ */
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
|
@ -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] */
|
||||
};
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
|
||||
message Subscription {
|
||||
optional string uri = 0x1;
|
||||
optional int32 expiry = 0x2;
|
||||
optional int32 status_code = 0x3;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
syntax = "proto2";
|
||||
|
||||
message Toplist {
|
||||
repeated string items = 0x1;
|
||||
}
|
||||
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
test1
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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__ */
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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__ */
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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);
|
|
@ -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
|
||||
|
|
|
@ -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__ */
|
155
src/transcode.c
155
src/transcode.c
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue