/// <summary> /// Change playlist container. The response comes as plain XML. /// </summary> public void SendChangePlaylistContainer(IChannelListener listener, PlaylistContainer playlistContainer, string xml) { /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Change-Playlist-Container-Channel", Channel.ChannelType.TYPE_PLAYLIST, listener); byte[] bytes = Encoding.UTF8.GetBytes(xml); ByteBuffer buffer = ByteBuffer.Allocate(2 + 16 + 1 + 4 + 4 + 4 + 1 + 1 + bytes.Length); /* Append channel id, playlist id and some bytes... */ buffer.PutShort((short)channel.Id); buffer.Put(Hex.ToBytes("00000000000000000000000000000000")); /* 16 bytes */ buffer.Put((byte)0x00); /* Playlists identifier. */ buffer.PutInt((int)playlistContainer.Revision); buffer.PutInt(playlistContainer.Playlists.Count); buffer.PutInt((int)playlistContainer.Checksum); buffer.Put((byte)0x00); /* Collaborative */ buffer.Put((byte)0x03); /* Unknown */ buffer.Put(bytes); buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_CHANGEPLAYLIST, buffer); }
/// <summary> /// Create playlist. The response comes as plain XML. /// </summary> public void SendCreatePlaylist(IChannelListener listener, Playlist playlist, string xml) { /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Change-Playlist-Channel", Channel.ChannelType.TYPE_PLAYLIST, listener); byte[] bytes = Encoding.UTF8.GetBytes(xml); ByteBuffer buffer = ByteBuffer.Allocate(2 + 16 + 1 + 4 + 4 + 4 + 1 + 1 + bytes.Length); /* Append channel id, playlist id and some bytes... */ buffer.PutShort((short)channel.Id); buffer.Put(Hex.ToBytes(playlist.Id)); /* 16 bytes */ buffer.Put((byte)0x02); /* Playlist identifier. */ buffer.PutInt(0); buffer.PutInt(0); buffer.PutInt(-1); /* -1: Create playlist. */ buffer.Put((byte)(playlist.IsCollaborative ? 0x01 : 0x00)); buffer.Put((byte)0x03); /* Unknown */ buffer.Put(bytes); buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_CHANGEPLAYLIST, buffer); }
/// <summary> /// Get a toplist. The response comes as GZIP compressed XML. /// </summary> public void SendToplistRequest(IChannelListener listener, Dictionary <string, string> paramargs) { /* Check if type parameter is present. */ if (!paramargs.ContainsKey("type")) { throw new ArgumentException("Parameter 'type' not given!"); } /* Create a map of parameters and calculate their length. */ Dictionary <byte[], byte[]> parameters = new Dictionary <byte[], byte[]>(); int parametersLength = 0; foreach (KeyValuePair <String, String> param in paramargs) { if (param.Key == null || param.Value == null) { continue; } byte[] key = Encoding.UTF8.GetBytes(param.Key); byte[] value = Encoding.UTF8.GetBytes(param.Value); parametersLength += 1 + 2 + key.Length + value.Length; parameters.Add(key, value); } /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Toplist-Channel", Channel.ChannelType.TYPE_TOPLIST, listener); ByteBuffer buffer = ByteBuffer.Allocate(2 + 2 + 2 + parametersLength); /* Append channel id, some values, query length and query. */ buffer.PutShort((short)channel.Id); buffer.PutInt(0x00000000); foreach (KeyValuePair <byte[], byte[]> parameter in parameters) { byte[] key = parameter.Key; byte[] value = parameter.Value; buffer.Put((byte)key.Length); buffer.PutShort((short)value.Length); buffer.Put(key); buffer.Put(value); } buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_GETTOPLIST, buffer); }
public void Load(string category, string hash, IChannelListener listener) { /* Load data in a separate thread, because we're an asynchronous load method. */ new Thread(delegate() { Channel channel = new Channel("Cached-Substream-Channel", ChannelType.TYPE_SUBSTREAM, null); listener.ChannelHeader(channel, null); listener.ChannelData(channel, Load(category, hash)); listener.ChannelEnd(channel); }).Start(); }
/// <summary> /// Get metadata for an artist (type = 1), album (type = 2) or a /// list of tracks (type = 3). The response comes as compressed XML. /// </summary> public void SendBrowseRequest(IChannelListener listener, int type, List <String> ids) { /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Browse-Channel", Channel.ChannelType.TYPE_BROWSE, listener); ByteBuffer buffer = ByteBuffer.Allocate(2 + 2 + 1 + ids.Count * 16 + ((type == 1 || type == 2) ? 4 : 0)); /* Check arguments. */ if (type != 1 && type != 2 && type != 3) { throw new ArgumentException("Type needs to be 1, 2 or 3."); } else if ((type == 1 && type == 2) && ids.Count != 1) { throw new ArgumentException("Types 1 and 2 only accept a single id."); } /* Append channel id and type. */ buffer.PutShort((short)channel.Id); buffer.PutShort((short)0x0000); /* Unknown. */ buffer.Put((byte)type); /* Append (16 byte binary, 32 byte hex string) ids. */ foreach (string id in ids) { /* Check length of id. */ if (id.Length != 32) { throw new ArgumentException("Id needs to have a length of 32."); } buffer.Put(Hex.ToBytes(id)); } /* Append zero. */ if (type == 1 || type == 2) { buffer.PutInt(0); /* Timestamp of cached version? */ } buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_BROWSE, buffer); }
/// <summary> /// Request ads. The response is GZIP compressed XML. /// </summary> public void SendAdRequest(IChannelListener listener, int type) { /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Ad-Channel", Channel.ChannelType.TYPE_AD, listener); ByteBuffer buffer = ByteBuffer.Allocate(2 + 1); /* Append channel id and ad type. */ buffer.PutShort((short)channel.Id); buffer.Put((byte)type); /* 0: audio, 1: banner, 2: fullscreen-banner, 3: unknown. */ buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_REQUESTAD, buffer); }
/// <summary> /// Request playlist details. The response comes as plain XML. /// </summary> public void SendPlaylistRequest(IChannelListener listener, string id) { /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Playlist-Channel", Channel.ChannelType.TYPE_PLAYLIST, listener); ByteBuffer buffer = ByteBuffer.Allocate(2 + 16 + 1 + 4 + 4 + 4 + 1); /* Check length of id. */ if (id != null && id.Length != 32) { throw new ArgumentException("Playlist id needs to have a length of 32."); } /* Append channel id, playlist id and some bytes... */ buffer.PutShort((short)channel.Id); /* Playlist container. */ if (id == null) { buffer.Put(Hex.ToBytes("00000000000000000000000000000000")); /* 16 bytes */ buffer.Put((byte)0x00); /* Playlist container identifier. */ } /* Normal playlist. */ else { buffer.Put(Hex.ToBytes(id)); /* 16 bytes */ buffer.Put((byte)0x02); /* Playlist identifier. */ } /* * TODO: Other playlist identifiers (e.g. 0x03, starred tracks? inbox?). */ /* TODO: Use those fields to request only the information needed. */ buffer.PutInt(-1); /* Revision. -1: no cached data. */ buffer.PutInt(0); /* Number of entries. */ buffer.PutInt(1); /* Checksum. */ buffer.Put((byte)0x00); /* Collaborative. */ buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_GETPLAYLIST, buffer); }
/// <summary> /// Request a part of the encrypted file from the server. /// /// The data should be decrypted using AES key in CTR mode /// with AES key provided and a static IV, incremented for /// each 16 byte data processed. /// </summary> public void SendSubstreamRequest(IChannelListener listener, Sharpotify.Media.File file, int offset, int length) { /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Substream-Channel", Channel.ChannelType.TYPE_SUBSTREAM, listener); ByteBuffer buffer = ByteBuffer.Allocate(2 + 2 + 2 + 2 + 2 + 2 + 2 + 4 + 20 + 4 + 4); /* Append channel id. */ buffer.PutShort((short)channel.Id); /* Unknown 10 bytes. */ buffer.PutShort((short)0x0800); buffer.PutShort((short)0x0000); buffer.PutShort((short)0x0000); buffer.PutShort((short)0x0000); buffer.PutShort((short)0x0000); buffer.PutShort((short)0x4e20); /* Unknown (static value) */ buffer.PutInt(200 * 1000); /* 20 bytes file id. */ buffer.Put(Hex.ToBytes(file.Id)); if (offset % 4096 != 0 || length % 4096 != 0 || length == 0) { throw new ArgumentException("Offset and length need to be a multiple of 4096."); } offset >>= 2; length >>= 2; /* Append offset and length. */ buffer.PutInt(offset); buffer.PutInt(offset + length); buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_GETSUBSTREAM, buffer); }
/// <summary> /// Request AES key for a track. /// </summary> public void SendAesKeyRequest(IChannelListener listener, Track track, Sharpotify.Media.File file) { /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("AES-Key-Channel", Channel.ChannelType.TYPE_AESKEY, listener); ByteBuffer buffer = ByteBuffer.Allocate(20 + 16 + 2 + 2 + 2); /* Request the AES key for this file by sending the file id and track id. */ buffer.Put(Hex.ToBytes(file.Id)); /* 20 bytes */ buffer.Put(Hex.ToBytes(track.Id)); /* 16 bytes */ buffer.PutShort((short)0x0000); buffer.PutShort((short)channel.Id); buffer.PutShort((short)0x0000); buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_REQKEY, buffer); }
/// <summary> /// Search music. The response comes as GZIP compressed XML. /// </summary> public void SendSearchQuery(IChannelListener listener, string query, int offset, int limit) { /* Create channel and buffer. */ byte[] queryBytes = Encoding.UTF8.GetBytes(query); Channel.Channel channel = new Channel.Channel("Search-Channel", Channel.ChannelType.TYPE_SEARCH, listener); ByteBuffer buffer = ByteBuffer.Allocate(2 + 2 + 6 * 4 + 2 + 1 + queryBytes.Length); /* Check offset and limit. */ if (offset < 0) { throw new ArgumentException("Offset needs to be >= 0"); } else if ((limit < 0 && limit != -1) || limit == 0) { throw new ArgumentException("Limit needs to be either -1 for no limit or > 0"); } /* Append channel id, some unknown values, query length and query. */ buffer.PutShort((short)channel.Id); buffer.PutShort((short)0x0000); /* Unknown. */ buffer.PutInt(offset); /* Track offset. */ buffer.PutInt(limit); /* Track limit. */ buffer.PutInt(offset); /* Album offset. */ buffer.PutInt(limit); /* Album limit. */ buffer.PutInt(offset); /* Artist offset. */ buffer.PutInt(limit); /* Artist limit. */ buffer.PutShort((short)0x0000); /* Unknown. */ buffer.Put((byte)queryBytes.Length); buffer.Put(queryBytes); buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_SEARCH, buffer); }
/// <summary> /// Request image using a 20 byte id. The response is a JPG. /// </summary> public void SendImageRequest(IChannelListener listener, string id) { /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Image-Channel", Channel.ChannelType.TYPE_IMAGE, listener); ByteBuffer buffer = ByteBuffer.Allocate(2 + 2 + 20); /* Check length of id. */ if (id.Length != 40) { throw new ArgumentException("Image id needs to have a length of 40."); } /* Append channel id and image hash. */ buffer.PutShort((short)channel.Id); buffer.PutShort((short)0x0000); buffer.Put(Hex.ToBytes(id)); buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_IMAGE, buffer); }
public void ChannelHeader(Channel channel, byte[] header) { /* Do nothing. */ }
public void ChannelError(Channel channel) { /* Do nothing. */ }
public void ChannelEnd(Channel channel) { this._offset += channel.DataLength; Channel.Unregister(channel.Id); }
public static void Register(Channel channel) { Channel._channels.Add(channel.Id, channel); }
public void ChannelHeader(Channel channel, byte[] header) { /* Ignore */ }
public void ChannelError(Channel channel) { this._done.Release(); }
public void ChannelData(Channel channel, byte[] data) { ByteBuffer buffer = new ByteBuffer(data); this._bytes += data.Length; this._buffers.Add(buffer); }
public void ChannelData(Channel channel, byte[] data) { /* Offsets needed for deinterleaving. */ int off, w, x, y, z; /* Copy data to cache buffer. */ for (int i = 0; i < data.Length; i++) { this._cacheData[this._channelTotal + i] = data[i]; } /* Allocate space for ciphertext. */ byte[] ciphertext = new byte[data.Length + 1024]; byte[] keystream = new byte[16]; /* Decrypt each 1024 byte block. */ for (int block = 0; block < data.Length / 1024; block++) { /* Deinterleave the 4x256 byte blocks. */ off = block * 1024; w = block * 1024 + 0 * 256; x = block * 1024 + 1 * 256; y = block * 1024 + 2 * 256; z = block * 1024 + 3 * 256; for (int i = 0; i < 1024 && (block * 1024 + i) < data.Length; i += 4) { ciphertext[off++] = data[w++]; ciphertext[off++] = data[x++]; ciphertext[off++] = data[y++]; ciphertext[off++] = data[z++]; } /* Decrypt 1024 bytes block. This will fail for the last block. */ for (int i = 0; i < 1024 && (block * 1024 + i) < data.Length; i += 16) { /* Produce 16 bytes of keystream from the IV. */ try { var crypt = _cipher.CreateEncryptor(_key, _iv); keystream = crypt.TransformFinalBlock(this._iv, 0, this._iv.Length); } catch (Exception) { } /* * Produce plaintext by XORing ciphertext with keystream. * And somehow I also need to XOR with the IV... Please * somebody tell me what I'm doing wrong, or is it the * Java implementation of AES? At least it works like this. */ // FIXME: Does the IV needs to be XORed in C# ? for (int j = 0; j < 16; j++) { //ciphertext[block * 1024 + i + j] ^= (byte)(keystream[j] ^ this._iv[j]); ciphertext[block * 1024 + i + j] ^= keystream[j]; } /* Update IV counter. */ for (int j = 15; j >= 0; j--) { this._iv[j] += 1; if ((int)(this._iv[j] & 0xFF) != 0) { break; } } /* Set new IV. */ this._cipher.IV = this._iv; } } /* Save data to output stream. */ try { off = 0; /* Check if we decoded the header yet. */ if (this._header == null) { /* Get header from data. */ byte[] bytes = new byte[167]; Array.Copy(ciphertext, bytes, bytes.Length); /* Decode header. */ this._header = new SpotifyOggHeader(bytes); off = 167; //Set stream length this._output.SetLength(this._header.Size); } this._output.WriteInternal(ciphertext, off, data.Length - off); //this._output.Write(ciphertext, off, data.Length - off); //this._output.Flush(); /* * Don't subtract 'off' here! Otherwise we would * accidentially close the stream in channelEnd! */ this._channelTotal += data.Length; } catch (Exception) { /* Don't care. */ } }
public void ChannelHeader(Channel channel, byte[] header) { this._cacheData = new byte[this._channelLength]; this._channelTotal = 0; }
public void ChannelEnd(Channel channel) { /* Create cache hash. */ string hash = this._cache.Hash(this._file, this._channelOffset, this._channelLength); /* Save to cache. */ if (this._cache != null && !this._cache.Contains("substream", hash)) { this._cache.Store("substream", hash, this._cacheData, this._channelTotal); } /* Send next substream request. */ try { if (this._channelTotal < this._channelLength) { this._output.AllAvailable = true; return; } this._channelOffset += this._channelLength; hash = this._cache.Hash(this._file, this._channelOffset, this._channelLength); if (this._cache != null && this._cache.Contains("substream", hash)) { this._cache.Load("substream", hash, this); } else { this._protocol.SendSubstreamRequest(this, this._file, this._channelOffset, this._channelLength); } } catch (Exception) { /* Ignore. */ } Channel.Unregister(channel.Id); }
/// <summary> /// Request replacements for a list of tracks. The response comes as compressed XML. /// </summary> public void SendReplacementRequest(IChannelListener listener, List <Track> tracks) { /* Calculate data length. */ int dataLength = 0; foreach (Track track in tracks) { if (track.Artist != null && track.Artist.Name != null) { dataLength += Encoding.UTF8.GetBytes(track.Artist.Name).Length; } if (track.Album != null && track.Album.Name != null) { dataLength += Encoding.UTF8.GetBytes(track.Album.Name).Length; } if (track.Title != null) { dataLength += Encoding.UTF8.GetBytes(track.Title).Length; } if (track.Length != -1) { dataLength += Encoding.UTF8.GetBytes((track.Length / 1000).ToString()).Length; } dataLength += 4; /* Separators */ } /* Create channel and buffer. */ Channel.Channel channel = new Channel.Channel("Browse-Channel", Channel.ChannelType.TYPE_BROWSE, listener); ByteBuffer buffer = ByteBuffer.Allocate(2 + 2 + 1 + dataLength); /* Append channel id and type. */ buffer.PutShort((short)channel.Id); buffer.PutShort((short)0x0000); /* Unknown. */ buffer.Put((byte)0x06); /* Append track info. */ foreach (Track track in tracks) { if (track.Artist != null && track.Artist.Name != null) { buffer.Put(Encoding.UTF8.GetBytes(track.Artist.Name)); } buffer.Put((byte)0x01); /* Separator. */ if (track.Album != null && track.Album.Name != null) { buffer.Put(Encoding.UTF8.GetBytes(track.Album.Name)); } buffer.Put((byte)0x01); /* Separator. */ if (track.Title != null) { buffer.Put(Encoding.UTF8.GetBytes(track.Title)); } buffer.Put((byte)0x01); /* Separator. */ if (track.Length != -1) { buffer.Put(Encoding.UTF8.GetBytes((track.Length / 1000).ToString())); } buffer.Put((byte)0x00); /* Separator. */ } buffer.Flip(); /* Register channel. */ Channel.Channel.Register(channel); /* Send packet. */ this.SendPacket(Command.COMMAND_BROWSE, buffer); }
public void ChannelData(Channel channel, byte[] data) { /* Offsets needed for deinterleaving. */ int off, w, x, y, z; /* Allocate space for ciphertext. */ byte[] ciphertext = new byte[data.Length + 1024]; byte[] keystream = new byte[16]; /* Decrypt each 1024 byte block. */ for (int block = 0; block < data.Length / 1024; block++) { /* Deinterleave the 4x256 byte blocks. */ off = block * 1024; w = block * 1024 + 0 * 256; x = block * 1024 + 1 * 256; y = block * 1024 + 2 * 256; z = block * 1024 + 3 * 256; for (int i = 0; i < 1024 && (block * 1024 + i) < data.Length; i += 4) { ciphertext[off++] = data[w++]; ciphertext[off++] = data[x++]; ciphertext[off++] = data[y++]; ciphertext[off++] = data[z++]; } /* Decrypt 1024 bytes block. This will fail for the last block. */ for (int i = 0; i < 1024 && (block * 1024 + i) < data.Length; i += 16) { /* Produce 16 bytes of keystream from the IV. */ try { var crypt = _cipher.CreateEncryptor(_key, _iv); keystream = crypt.TransformFinalBlock(this._iv, 0, this._iv.Length); } catch (Exception) { } /* * Produce plaintext by XORing ciphertext with keystream. * And somehow I also need to XOR with the IV... Please * somebody tell me what I'm doing wrong, or is it the * Java implementation of AES? At least it works like this. */ // FIXME: Does the IV needs to be XORed in C# ? for (int j = 0; j < 16; j++) { ciphertext[block * 1024 + i + j] ^= (byte)(keystream[j] ^ this._iv[j]); } /* Update IV counter. */ for (int j = 15; j >= 0; j--) { this._iv[j] += 1; if ((int)(this._iv[j] & 0xFF) != 0) { break; } } /* Set new IV. */ this._cipher.IV = this._iv; } } /* Write data to output stream. */ try { this._outputStream.Write(ciphertext, 0, ciphertext.Length - 1024); } catch (Exception) { /* Just don't care... */ } }
public void ChannelError(Channel channel) { /* Ignore */ }
public void ChannelEnd(Channel channel) { Channel.Unregister(channel.Id); this._done.Release(); }