owntone-server/src/inputs/librespot-c/src/librespot-c-internal.h

370 lines
8.2 KiB
C

#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 <time.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
// After a disconnect we try to reconnect, but if we are disconnected yet again
// we get the hint and won't try reconnecting again until after this cooldown
#define SP_AP_COOLDOWN_SECS 30
// 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
// Used to create default sysinfo, which should be librespot_[short sha]_[random 8 characters build id],
// ref https://github.com/plietar/librespot/pull/218. User may override, but
// as of 20220516 Spotify seems to have whitelisting of client name.
#define SP_CLIENT_NAME_DEFAULT "librespot"
#define SP_CLIENT_VERSION_DEFAULT "000000"
#define SP_CLIENT_BUILD_ID_DEFAULT "aabbccdd"
// 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,
};
enum sp_channel_state
{
SP_CHANNEL_STATE_UNALLOCATED = 0,
SP_CHANNEL_STATE_OPENED,
SP_CHANNEL_STATE_PLAYING,
SP_CHANNEL_STATE_PAUSED,
SP_CHANNEL_STATE_STOPPED,
};
// 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;
enum sp_channel_state state;
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;
time_t cooldown_ts;
// Address of an access point we want to avoid due to previous failure
char *ap_avoid;
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, next and subsequent message being processed
enum sp_msg_type msg_type_last;
enum sp_msg_type msg_type_next;
enum sp_msg_type msg_type_queued;
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__