#30 - WIP on the shoutcast v2 protocol

This commit is contained in:
Denny
2016-09-29 20:50:00 -04:00
parent 6138838d60
commit 02f470cf91
28 changed files with 2183 additions and 118 deletions

View File

@@ -10,6 +10,7 @@
<w>talkgroup</w>
<w>tgid</w>
<w>ultravox</w>
<w>xtea</w>
</words>
</dictionary>
</component>

View File

@@ -65,6 +65,11 @@ public enum BroadcastState
*/
UNKNOWN_HOST("Unknown Host or Invalid Port", true),
/**
* Unsupported audio format
*/
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true),
/**
* Unspecified error
*/

View File

@@ -20,6 +20,8 @@ package audio.broadcast;
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import audio.broadcast.shoutcast.v1.ShoutcastV1Handler;
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
import audio.broadcast.shoutcast.v2.ShoutcastV2Handler;
import audio.convert.IAudioConverter;
import audio.convert.MP3AudioConverter;
import controller.ThreadPoolManager;
@@ -51,6 +53,9 @@ public class BroadcasterFactory
case SHOUTCAST_V1:
return new Broadcaster(threadPoolManager,
new ShoutcastV1Handler((ShoutcastV1Configuration)configuration, converter));
case SHOUTCAST_V2:
return new Broadcaster(threadPoolManager,
new ShoutcastV2Handler((ShoutcastV2Configuration)configuration, converter));
case UNKNOWN:
break;
}
@@ -70,7 +75,6 @@ public class BroadcasterFactory
{
case MP3:
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_NO_VARIABLE_BITRATE);
}
return null;

View File

@@ -133,12 +133,12 @@ public class ShoutcastV1Handler extends BroadcastHandler
{
if(!connected() && System.currentTimeMillis() - mLastConnectionAttempt >= RECONNECT_INTERVAL_MILLISECONDS)
{
mLastConnectionAttempt = System.currentTimeMillis();
createSocket();
if(mSocket.isConnected())
{
mLog.debug("Sending credentials");
StringBuilder sb = new StringBuilder();
//Password
@@ -186,7 +186,6 @@ public class ShoutcastV1Handler extends BroadcastHandler
*/
private void send(String data) throws IOException
{
mLog.debug("Sending [" + data + "]");
if(data != null && !data.isEmpty() && mOutputStream != null)
{
mOutputStream.writeBytes(data);
@@ -200,7 +199,6 @@ public class ShoutcastV1Handler extends BroadcastHandler
*/
private void send(byte[] data) throws IOException
{
mLog.debug("Sending bytes:" + data.length);
if(connected())
{
int sent = 0;
@@ -344,7 +342,7 @@ public class ShoutcastV1Handler extends BroadcastHandler
config.setAlias("Test Configuration");
config.setHost("localhost");
config.setPort(8000);
config.setPassword("denny3");
config.setPassword("denny3:#1");
config.setStreamName("Denny's Audio Broadcast Test");
config.setGenre("Scanner Audio");
config.setPublic(true);

View File

@@ -24,14 +24,13 @@ import audio.broadcast.BroadcastServerType;
public class ShoutcastV2Configuration extends BroadcastConfiguration
{
private String mMountPoint;
private String mStreamName;
private String mSongTitle;
private String mGenre;
private boolean mPublic;
private int mChannels;
private int mStreamID;
private String mUserID;
private int mBitRate;
private String mStreamName;
private String mStreamGenre;
private String mURL;
private boolean mPublic = true;
public ShoutcastV2Configuration(BroadcastFormat format)
@@ -42,26 +41,60 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.SHOUTCAST_V1;
}
public String getMountPoint()
{
return mMountPoint;
return BroadcastServerType.SHOUTCAST_V2;
}
/**
* Sets the optional mount point. Shoutcast v1.x servers don't require a mount point, but v2.x servers do require
* the mount point.
*
* Note: if the supplied mount point contains a leading slash (/) it will be removed.
*
* @param mountPoint
* Stream ID
*/
public void setMountPoint(String mountPoint)
public int getStreamID()
{
//Strip the leading slash from the mount point if the user supplies it
mMountPoint = mountPoint.replace("/", "");
return mStreamID;
}
/**
* Sets the stream ID
*
* @param streamID numeric stream identifier between 1 - 2147483647
*/
public void setStreamID(int streamID)
{
assert(1 <= streamID && streamID <= Integer.MAX_VALUE);
mStreamID = streamID;
}
/**
* User ID
*/
public String getUserID()
{
return mUserID;
}
/**
* Sets the user ID
* @param userID
*/
public void setUserID(String userID)
{
mUserID = userID;
}
/**
* Bit rate in kilobits per second
*/
public int getBitRate()
{
return mBitRate;
}
/**
* Bit rate
* @param bitRate in kilobits per second
*/
public void setBitRate(int bitRate)
{
mBitRate = bitRate;
}
/**
@@ -81,37 +114,20 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
mStreamName = name;
}
/**
* Song title
*/
public String getSongTitle()
{
return mSongTitle;
}
/**
* Set the song title
*/
public void setSongTitle(String title)
{
mSongTitle = title;
}
/**
* Stream genre
*/
public String getGenre()
public String getStreamGenre()
{
return mGenre;
return mStreamGenre;
}
/**
* Stream genre
* @param genre
* Sets the genre tag for this stream
*/
public void setGenre(String genre)
public void setStreamGenre(String genre)
{
mGenre = genre;
mStreamGenre = genre;
}
/**
@@ -131,39 +147,6 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
mPublic = isPublic;
}
/**
* Number of audio channels in the broadcast
*/
public int getChannels()
{
return mChannels;
}
/**
* Sets the number of audio channels in the broadcast
*/
public void setChannels(int channels)
{
mChannels = channels;
}
/**
* Bit rate in bits per second
*/
public int getBitRate()
{
return mBitRate;
}
/**
* Bit rate
* @param bitRate in samples per second
*/
public void setBitRate(int bitRate)
{
mBitRate = bitRate;
}
/**
* URL associated with the broadcast where users can find additional details.
*/

View File

@@ -0,0 +1,570 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2;
import audio.AudioPacket;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastHandler;
import audio.broadcast.BroadcastState;
import audio.broadcast.Broadcaster;
import audio.broadcast.BroadcasterFactory;
import audio.broadcast.shoutcast.v2.message.AuthenticateBroadcast;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyGenre;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyName;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyPublic;
import audio.broadcast.shoutcast.v2.message.ConfigureIcyURL;
import audio.broadcast.shoutcast.v2.message.MP3Audio;
import audio.broadcast.shoutcast.v2.message.NegotiateBufferSize;
import audio.broadcast.shoutcast.v2.message.NegotiateMaxPayloadSize;
import audio.broadcast.shoutcast.v2.message.RequestCipher;
import audio.broadcast.shoutcast.v2.message.SetupBroadcast;
import audio.broadcast.shoutcast.v2.message.Standby;
import audio.broadcast.shoutcast.v2.message.UltravoxMessage;
import audio.broadcast.shoutcast.v2.message.UltravoxMessageFactory;
import audio.broadcast.shoutcast.v2.message.UltravoxMessageType;
import audio.broadcast.shoutcast.v2.message.UltravoxMetadata;
import audio.broadcast.shoutcast.v2.message.XMLMetadata;
import audio.convert.IAudioConverter;
import controller.ThreadPoolManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.wave.AudioPacketMonoWaveReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ShoutcastV2Handler extends BroadcastHandler
{
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2Handler.class );
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
private long mLastConnectionAttempt = 0;
private Socket mSocket;
private DataOutputStream mOutputStream;
private DataInputStream mInputStream;
private int mMaxPayloadSize = 16377;
/**
* Creates a Shoutcast Version 1 broadcast handler.
* @param configuration
* @param audioConverter
*/
public ShoutcastV2Handler(ShoutcastV2Configuration configuration, IAudioConverter audioConverter)
{
super(configuration, audioConverter);
}
/**
* Shoutcast V2 Configuration information
*/
private ShoutcastV2Configuration getShoutcastConfiguration()
{
return (ShoutcastV2Configuration)getBroadcastConfiguration();
}
/**
* Broadcast audio packets
*
* @param audioPackets
*/
@Override
public void broadcast(List<AudioPacket> audioPackets)
{
//If we're connected, send the audio, otherwise discard it
if(connect())
{
byte[] convertedAudio = mAudioConverter.convert(audioPackets);
if(convertedAudio != null)
{
List<UltravoxMessage> audioMessages = getAudioMessages(convertedAudio);
for(UltravoxMessage audioMessage: audioMessages)
{
try
{
send(audioMessage, false);
}
catch(IOException e)
{
mLog.error("Error while dispatching audio", e);
setBroadcastState(BroadcastState.BROADCAST_ERROR);
return;
}
}
}
}
else
{
mLog.debug("Not connected");
}
}
/**
* Indicates if the audio handler is currently connected to the remote server and capable of streaming audio.
*
* This method will attempt to establish a connection or reconnect to the streaming server.
* @return true if the audio handler can stream audio
*/
private boolean connect()
{
if(!connected())
{
createConnection();
}
return connected();
}
/**
* Disconnect from the remote server
*/
@Override
public void disconnect()
{
// TerminateBroadcast terminate = (TerminateBroadcast)UltravoxMessageFactory
// .getMessage(UltravoxMessageType.TERMINATE_BROADCAST);
// response = send(terminate, out, in);
// mLog.debug("Terminate Response:" + (response.getPayload() != null ? response.getPayload() : ""));
}
/**
* Sends the message. When get response is true, reads from the input stream and converts the response to a
* message that is returned.
*
* @param message to send to the shoutcast/ultravox server
* @param getResponse true if the sent message will generate a response
* @return response message when getResponse is set to true
* @throws IOException if there is an issue while communicating with the server
*/
private UltravoxMessage send(UltravoxMessage message, boolean getResponse) throws IOException
{
byte[] bytes = message.getMessage();
mOutputStream.write(bytes);
if(getResponse)
{
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
int available = mInputStream.available();
if(available > 0)
{
byte[] buffer = new byte[available];
int read = mInputStream.read(buffer);
return UltravoxMessageFactory.getMessage(buffer);
}
}
return null;
}
/**
* Loads the encoded audio byte array into a list of ultravox messages ready for transmission
* @param audio to embed as message payload
* @return list of messages containing the audio payload
*/
private List<UltravoxMessage> getAudioMessages(byte[] audio)
{
List<UltravoxMessage> messages = new ArrayList<>();
int pointer = 0;
while(pointer < audio.length)
{
int payloadSize = Math.min(mMaxPayloadSize, audio.length - pointer);
byte[] payload = new byte[payloadSize];
System.arraycopy(audio, pointer, payload, 0, payloadSize);
MP3Audio mp3Audio = (MP3Audio)UltravoxMessageFactory.getMessage(UltravoxMessageType.MP3_DATA);
mp3Audio.setPayload(payload);
messages.add(mp3Audio);
pointer += payloadSize;
}
return messages;
}
/**
* Encodes the list of metadata in one or more Ultravox XMLMetadata messages according to the maximum negotiated
* payload size for the current connection. Each entry in the list of metadata strings should be an xml encoded
* value: <tag>value</tag>
*
* See UltravoxMetadata.TAG.asXML(String value)
*
* @param xmlMetadata list of xml encoded values
* @return a sequence of XMLMetadata messages sufficient to carry the complete set of metadata
*/
private List<UltravoxMessage> getMetadataMessages(List<String> xmlMetadata)
{
List<UltravoxMessage> messages = new ArrayList<>();
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<metadata>\n");
for(String metadata: xmlMetadata)
{
sb.append(metadata);
}
sb.append("\n</metadata>");
mLog.debug("Metadata:" + sb.toString());
byte[] xml = sb.toString().getBytes();
int pointer = 0;
int messageCounter = 1;
int messageCount = (int)Math.ceil((double)xml.length / (double)(mMaxPayloadSize - 6));
if(messageCount > 32)
{
messageCount = 32; //Max number of metadata messages in a sequence
}
while(pointer < xml.length && messageCounter <= messageCount)
{
int payloadSize = Math.min(mMaxPayloadSize - 6, xml.length - pointer);
byte[] payload = new byte[payloadSize + 6];
payload[1] = (byte)(0x01);
payload[3] = (byte)(messageCount & 0xFF);
payload[5] = (byte)(messageCounter & 0xFF);
System.arraycopy(xml, pointer, payload, 6, payloadSize);
XMLMetadata message = (XMLMetadata)UltravoxMessageFactory.getMessage(UltravoxMessageType.XML_METADATA);
message.setPayload(payload);
messages.add(message);
pointer += payloadSize;
}
return messages;
}
/**
* Creates a connnection to the remote server using the shoutcast configuration information. Once disconnected
* following a successful connection, attempts to reestablish a connection on a set interval
*/
private void createConnection()
{
if(!connected() && System.currentTimeMillis() - mLastConnectionAttempt >= RECONNECT_INTERVAL_MILLISECONDS)
{
mLastConnectionAttempt = System.currentTimeMillis();
createSocket();
if (mSocket.isConnected())
{
RequestCipher requestCipher = (RequestCipher) UltravoxMessageFactory
.getMessage(UltravoxMessageType.REQUEST_CIPHER);
try
{
UltravoxMessage response = send(requestCipher, true);
if (response instanceof RequestCipher)
{
RequestCipher cipherResponse = (RequestCipher) response;
AuthenticateBroadcast authenticateBroadcast = (AuthenticateBroadcast) UltravoxMessageFactory
.getMessage(UltravoxMessageType.AUTHENTICATE_BROADCAST);
authenticateBroadcast.setCredentials(cipherResponse.getCipher(),
getShoutcastConfiguration().getStreamID(), getShoutcastConfiguration().getUserID(),
getShoutcastConfiguration().getPassword());
response = send(authenticateBroadcast, true);
if (response.isErrorResponse())
{
setBroadcastState(BroadcastState.INVALID_PASSWORD);
return;
}
UltravoxMessage streamMimeType = UltravoxMessageFactory
.getMessage(UltravoxMessageType.STREAM_MIME_TYPE);
streamMimeType.setPayload(getShoutcastConfiguration().getBroadcastFormat().getValue());
response = send(streamMimeType, true);
if (response.isErrorResponse())
{
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
mLog.error("Audio format [" + getShoutcastConfiguration().getBroadcastFormat() + "] not supported");
return;
}
SetupBroadcast setupBroadcast = (SetupBroadcast) UltravoxMessageFactory
.getMessage(UltravoxMessageType.SETUP_BROADCAST);
setupBroadcast.setBitRate(getShoutcastConfiguration().getBitRate(),
getShoutcastConfiguration().getBitRate());
response = send(setupBroadcast, true);
if (response.isErrorResponse())
{
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
mLog.error("Audio bit rate [" + getShoutcastConfiguration().getBitRate() + "] not supported");
return;
}
NegotiateMaxPayloadSize negotiateMaxPayloadSize = (NegotiateMaxPayloadSize) UltravoxMessageFactory
.getMessage(UltravoxMessageType.NEGOTIATE_MAX_PAYLOAD_SIZE);
negotiateMaxPayloadSize.setMaximumPayloadSize(mMaxPayloadSize, 14000);
response = send(negotiateMaxPayloadSize, true);
mMaxPayloadSize = ((NegotiateMaxPayloadSize) response).getMaximumPayloadSize();
List<String> metadata = new ArrayList<>();
if (getShoutcastConfiguration().getStreamName() != null)
{
metadata.add(UltravoxMetadata.TITLE_1.asXML(getShoutcastConfiguration().getStreamName()));
ConfigureIcyName icyName = (ConfigureIcyName) UltravoxMessageFactory
.getMessage(UltravoxMessageType.CONFIGURE_ICY_NAME);
icyName.setName(getShoutcastConfiguration().getStreamName());
response = send(icyName, false);
}
if (getShoutcastConfiguration().getStreamGenre() != null)
{
metadata.add(UltravoxMetadata.GENRE.asXML(getShoutcastConfiguration().getStreamGenre()));
ConfigureIcyGenre icyGenre = (ConfigureIcyGenre) UltravoxMessageFactory
.getMessage(UltravoxMessageType.CONFIGURE_ICY_GENRE);
icyGenre.setGenre(getShoutcastConfiguration().getStreamGenre());
response = send(icyGenre, false);
}
if (getShoutcastConfiguration().getURL() != null)
{
ConfigureIcyURL icyURL = (ConfigureIcyURL) UltravoxMessageFactory
.getMessage(UltravoxMessageType.CONFIGURE_ICY_URL);
icyURL.setURL(getShoutcastConfiguration().getURL());
response = send(icyURL, false);
}
ConfigureIcyPublic icyPublic = (ConfigureIcyPublic) UltravoxMessageFactory
.getMessage(UltravoxMessageType.CONFIGURE_ICY_PUBLIC);
icyPublic.setPublic(getShoutcastConfiguration().isPublic());
response = send(icyPublic, false);
Standby standby = (Standby) UltravoxMessageFactory.getMessage(UltravoxMessageType.STANDBY);
response = send(standby, true);
if (response.isValidResponse())
{
setBroadcastState(BroadcastState.CONNECTED);
}
else
{
setBroadcastState(BroadcastState.DISCONNECTED);
mLog.error("Error message after configuration and sending standby:" +
(response.getError() != null ? response.getError() : "no error message"));
}
metadata.add(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION.asXML("sdrtrunk"));
mLog.debug("Fetching metadata messages from metadata...");
List<UltravoxMessage> messages = getMetadataMessages(metadata);
for (UltravoxMessage message : messages)
{
mLog.debug("Sending metadata message");
send(message, true);
}
mLog.debug("We're connected!!!");
}
}
catch (IOException e)
{
setBroadcastState(BroadcastState.ERROR);
mLog.error("Error while creating connection to server", e);
}
}
}
}
/**
* Creates a socket connection to the remote server and sets the state to CONNECTING.
*/
private void createSocket()
{
if(mSocket == null)
{
try
{
mSocket = new Socket(getShoutcastConfiguration().getHost(),
getShoutcastConfiguration().getPort());
mOutputStream = new DataOutputStream(mSocket.getOutputStream());
mInputStream = new DataInputStream(mSocket.getInputStream());
}
catch(UnknownHostException uhe)
{
setBroadcastState(BroadcastState.UNKNOWN_HOST);
mLog.error("Unknown host or port. Unable to create connection to streaming server host[" +
getShoutcastConfiguration().getHost() + "] and port[" +
getShoutcastConfiguration().getPort() + "] - will reattempt connection periodically");
return;
}
catch(IOException ioe)
{
setBroadcastState(BroadcastState.ERROR);
mLog.error("Error connecting to streaming server host[" +
getShoutcastConfiguration().getHost() + "] and port[" +
getShoutcastConfiguration().getPort() + "]", ioe);
return;
}
}
if(mSocket.isConnected())
{
setBroadcastState(BroadcastState.CONNECTING);
}
else
{
try
{
SocketAddress address = getShoutcastConfiguration().getAddress();
mSocket.connect(address);
setBroadcastState(BroadcastState.CONNECTING);
}
catch(UnknownHostException uhe)
{
setBroadcastState(BroadcastState.UNKNOWN_HOST);
}
catch(IOException e)
{
setBroadcastState(BroadcastState.ERROR);
mLog.error("Error connecting to streaming server host[" +
getShoutcastConfiguration().getHost() + "] and port[" +
getShoutcastConfiguration().getPort() + "]", e);
}
}
}
public static UltravoxMessage send(UltravoxMessage message,
DataOutputStream out,
DataInputStream in,
int maxPayloadSize) throws IOException
{
byte[] bytes = message.getMessage();
out.write(bytes);
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
int available = in.available();
if(available > 0)
{
byte[] buffer = new byte[available];
int read = in.read(buffer);
return UltravoxMessageFactory.getMessage(buffer);
}
return null;
}
public static void main(String[] args)
{
boolean test = false;
ShoutcastV2Configuration config = new ShoutcastV2Configuration(BroadcastFormat.MP3);
config.setStreamID(1);
config.setAlias("Test Configuration");
config.setHost("localhost");
config.setPort(8000);
config.setPassword("denny3");
config.setStreamName("Denny's Audio Broadcast Test");
config.setStreamGenre("Public Safety");
config.setPublic(true);
config.setURL("http://localhost:8000");
config.setBitRate(16);
if(test)
{
}
else
{
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
final Broadcaster broadcaster = BroadcasterFactory.getBroadcaster(threadPoolManager,config);
Path path = Paths.get("/home/denny/Music/PCM.wav");
mLog.debug("Opening: " + path.toString());
mLog.debug("Registering and starting audio playback");
while(true)
{
mLog.debug("Playback started [" + path.toString() + "]");
try (AudioPacketMonoWaveReader reader = new AudioPacketMonoWaveReader(path, true))
{
reader.setListener(broadcaster);
reader.read();
}
catch (IOException e)
{
mLog.error("Error", e);
}
mLog.debug("Playback ended [" + path.toString() + "]");
}
}
}
}

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
import util.XTEA;
import java.util.Arrays;
public class AuthenticateBroadcast extends UltravoxMessage
{
/**
* Client request to server to send authentication credentials
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
AuthenticateBroadcast()
{
super(UltravoxMessageType.AUTHENTICATE_BROADCAST);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
AuthenticateBroadcast(byte[] data)
{
super(data);
}
/**
* Set credential to authenticate the stream with the server
*
* @param encryptionKey received from the server in the RequestCipher message
* @param streamID that we're going to stream to
* @param userID for the stream. Note: this is optional and can be null. The server currently ignores this value.
* @param password for the stream.
*/
public void setCredentials(String encryptionKey, int streamID, String userID, String password)
{
XTEA xtea = new XTEA(encryptionKey);
StringBuilder sb = new StringBuilder();
sb.append(SHOUTCAST_VERSION);
sb.append(":");
sb.append(streamID);
sb.append(":");
sb.append(userID != null ? encrypt(xtea, userID) : "");
sb.append(":");
sb.append(password != null ? encrypt(xtea, password) : "");
setPayload(sb.toString());
}
/**
* Encrypts the value using the XTEA block cipher algorithm and returns the encrypted value as a 16 hex value
*
* @param xtea instance of the XTEA algorithm with a loaded encryption key
* @param value to encode
* @return encrypted password as 64-bit, 16 character hexadecimal value
*/
private static String encrypt(XTEA xtea, String value)
{
byte[] valueBytes = value.getBytes();
if(valueBytes.length % 8 != 0 || valueBytes.length == 0)
{
int newLength = ((valueBytes.length / 8) + 1) * 8;
valueBytes = Arrays.copyOf(valueBytes, newLength);
}
byte[] encrypted = xtea.encrypt(valueBytes);
StringBuilder sb = new StringBuilder();
for(byte b: encrypted)
{
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class ConfigureIcyGenre extends UltravoxMessage
{
/**
* Client request to server to configure the ICY-GENRE metadata
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
ConfigureIcyGenre()
{
super(UltravoxMessageType.CONFIGURE_ICY_GENRE);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
ConfigureIcyGenre(byte[] data)
{
super(data);
}
public void setGenre(String genre)
{
setPayload(genre);
}
}

View File

@@ -0,0 +1,52 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
import util.XTEA;
import java.util.Arrays;
public class ConfigureIcyName extends UltravoxMessage
{
/**
* Client request to server to configure the ICY-NAME metadata
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
ConfigureIcyName()
{
super(UltravoxMessageType.CONFIGURE_ICY_NAME);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
ConfigureIcyName(byte[] data)
{
super(data);
}
public void setName(String name)
{
setPayload(name);
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class ConfigureIcyPublic extends UltravoxMessage
{
/**
* Client request to server to configure the ICY-PUBLIC metadata
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
ConfigureIcyPublic()
{
super(UltravoxMessageType.CONFIGURE_ICY_PUBLIC);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
ConfigureIcyPublic(byte[] data)
{
super(data);
}
public void setPublic(boolean publicStream)
{
setPayload(publicStream ? "1" : "0");
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class ConfigureIcyURL extends UltravoxMessage
{
/**
* Client request to server to configure the ICY-URL metadata
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
ConfigureIcyURL()
{
super(UltravoxMessageType.CONFIGURE_ICY_URL);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
ConfigureIcyURL(byte[] data)
{
super(data);
}
public void setURL(String url)
{
setPayload(url);
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class FlushCachedMetadata extends UltravoxMessage
{
/**
* Client request to server to flush any currently cached stream metadata
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
FlushCachedMetadata()
{
super(UltravoxMessageType.FLUSH_CACHED_METADATA);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
FlushCachedMetadata(byte[] data)
{
super(data);
}
}

View File

@@ -16,34 +16,29 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2;
package audio.broadcast.shoutcast.v2.message;
public enum UltravoxMessageType
public class MP3Audio extends UltravoxMessage
{
AUTHENTICATE_BROADCAST(0x1001),
UNKNOWN(0x0000);
private int mValue;
private UltravoxMessageType(int value)
/**
* Client request to server to send authentication credentials
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
MP3Audio()
{
mValue = value;
super(UltravoxMessageType.MP3_DATA);
}
public int getValue()
{
return mValue;
}
public static UltravoxMessageType fromValue(int value)
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
MP3Audio(byte[] data)
{
switch(value)
{
case 0x1001:
return AUTHENTICATE_BROADCAST;
default:
return UNKNOWN;
}
super(data);
}
}

View File

@@ -0,0 +1,82 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class NegotiateBufferSize extends UltravoxMessage
{
/**
* Client request to server to negotiate the server's buffer size
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
NegotiateBufferSize()
{
super(UltravoxMessageType.NEGOTIATE_BUFFER_SIZE);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
NegotiateBufferSize(byte[] data)
{
super(data);
}
/**
* Sets the desired and minimum requested buffer sizes to send to the server
* @param desired buffer size in kilobytes
* @param minimum buffer size in kilobytes
*/
public void setBufferSize(int desired, int minimum)
{
StringBuilder sb = new StringBuilder();
sb.append(desired).append(":").append(minimum);
setPayload(sb.toString());
}
/**
* Negotiated buffer size from a server response message.
*
* @return server negotiated buffer size, or zero if this is not a server response message, or this is an error
* server response message, or the server response message buffer size cannot be determined from the message payload.
*/
public int getBufferSize()
{
String payload = getPayload();
if(payload != null && payload.startsWith("ACK:"))
{
try
{
int bufferSize = Integer.parseInt(payload.substring(4));
return bufferSize;
}
catch(Exception e)
{
//Do nothing, we couldn't parse the value
}
}
return 0;
}
}

View File

@@ -0,0 +1,87 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class NegotiateMaxPayloadSize extends UltravoxMessage
{
/**
* Client request to server to negotiate maximum payload size
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
NegotiateMaxPayloadSize()
{
super(UltravoxMessageType.NEGOTIATE_MAX_PAYLOAD_SIZE);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
NegotiateMaxPayloadSize(byte[] data)
{
super(data);
}
/**
* Sets the maximum payload size.
*
* @param desiredMaximum payload size (no larger than 16377)
* @param minimum acceptable payload size (no larger than 16377)
*/
public void setMaximumPayloadSize(int desiredMaximum, int minimum)
{
assert(desiredMaximum <= 16377);
assert(minimum <= 16377);
StringBuilder sb = new StringBuilder();
sb.append(desiredMaximum).append(":").append(minimum);
setPayload(sb.toString());
}
/**
* Negotiated maximum payload size from a server response message.
*
* @return server negotiated maximum payload size, or zero if this is not a server response message, or this is an
* error server response message, or the server response message maximum payload size cannot be determined from the
* message payload.
*/
public int getMaximumPayloadSize()
{
String payload = getPayload();
if(payload != null && payload.startsWith("ACK:"))
{
try
{
int maximumPayloadSize = Integer.parseInt(payload.substring(4));
return maximumPayloadSize;
}
catch(Exception e)
{
//Do nothing, we couldn't parse the value
}
}
return 0;
}
}

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class RequestCipher extends UltravoxMessage
{
/**
* Client request to server for encryption key
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
RequestCipher()
{
super(UltravoxMessageType.REQUEST_CIPHER);
setPayload(SHOUTCAST_VERSION);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
RequestCipher(byte[] data)
{
super(data);
}
/**
* Cipher key returned from the server to use for encrypting user id and password credentials
*
* @return cipher key or null
*/
public String getCipher()
{
if(isValidResponse())
{
return getPayload().replace(VALID_RESPONSE_PAYLOAD_PREFIX, "");
}
return null;
}
}

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class SetupBroadcast extends UltravoxMessage
{
/**
* Client request to server to specify the stream bit rate
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
SetupBroadcast()
{
super(UltravoxMessageType.SETUP_BROADCAST);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
SetupBroadcast(byte[] data)
{
super(data);
}
/**
* Sets the average and maximum bit rates. Set both to the same value for a Constant Bit Rate (CBR) stream.
*
* @param average bit rate of the stream in kbps
* @param maximum bit rate of the stream in kbps
*/
public void setBitRate(int average, int maximum)
{
StringBuilder sb = new StringBuilder();
sb.append(average).append(":").append(maximum);
setPayload(sb.toString());
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class Standby extends UltravoxMessage
{
/**
* Client request to server to notify that configuration is complete, prepare for streaming
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
Standby()
{
super(UltravoxMessageType.STANDBY);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
Standby(byte[] data)
{
super(data);
}
}

View File

@@ -0,0 +1,55 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
import audio.broadcast.BroadcastFormat;
public class StreamMimeType extends UltravoxMessage
{
/**
* Client request to server to set stream MIME type
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
StreamMimeType()
{
super(UltravoxMessageType.STREAM_MIME_TYPE);
}
/**
* Server response to client request
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
StreamMimeType(byte[] data)
{
super(data);
}
/**
* Sets the broadcast format or MIME type
* @param format of the stream
*/
public void setFormat(BroadcastFormat format)
{
setPayload(format.getValue());
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class TerminateBroadcast extends UltravoxMessage
{
/**
* Client request to server to terminate the broadcast
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
TerminateBroadcast()
{
super(UltravoxMessageType.TERMINATE_BROADCAST);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
TerminateBroadcast(byte[] data)
{
super(data);
}
}

View File

@@ -16,13 +16,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2;
package audio.broadcast.shoutcast.v2.message;
import bits.BinaryMessage;
import com.sun.org.apache.regexp.internal.RE;
import java.util.Arrays;
public abstract class UltravoxMessage
{
public static final String SHOUTCAST_VERSION = "2.1";
public static final String VALID_RESPONSE_PREFIX = "ACK";
public static final String VALID_RESPONSE_PAYLOAD_PREFIX = "ACK:";
public static final String ERROR_RESPONSE_PREFIX = "NAK:";
public static final int[] SYNC = {0,1,2,3,4,5,6,7};
public static final int[] RESERVED = {8,9,10,11};
public static final int REQUIRED_DELIVERY = 12;
@@ -38,25 +44,45 @@ public abstract class UltravoxMessage
*/
public UltravoxMessage(byte[] data)
{
mMessage = new BinaryMessage(data);
mMessage = new BinaryMessage(data.length * 8);
int pointer = 0;
for(byte b: data)
{
mMessage.setByte(pointer, b);
pointer += 8;
}
}
/**
* Constructs an empty message of the specified length
*
* @param ultravoxMessageType type of message
* @param length of message in bits (64 minimum)
*/
public UltravoxMessage(UltravoxMessageType ultravoxMessageType, int length)
public UltravoxMessage(UltravoxMessageType ultravoxMessageType)
{
assert(length >= 64);
mMessage = new BinaryMessage(length);
mMessage = new BinaryMessage(56);
setSync();
setMessageType(ultravoxMessageType);
}
/**
* Message byte array
*
* @return message bytes
*/
public byte[] getMessage()
{
return mMessage.toByteArray();
}
public String toString()
{
return mMessage.toString();
}
/**
* Sets the message sync bits to the predefined sync pattern: 01011010 (0x5A)
*/
@@ -138,8 +164,25 @@ public abstract class UltravoxMessage
*/
public String getPayload()
{
//TODO: parse payload field
return null;
int payloadLength = mMessage.getInt(PAYLOAD_LENGTH);
byte[] payload = new byte[payloadLength];
int payloadPointer = 48;
for(int x = 0; x < payloadLength; x++)
{
payload[x] = mMessage.getByte(payloadPointer);
payloadPointer += 8;
}
//Strip the trailing 0x00 null value from a server response
if(payload[payload.length - 1] == 0)
{
return new String(Arrays.copyOf(payload, payload.length - 1));
}
return new String(payload);
}
/**
@@ -147,11 +190,50 @@ public abstract class UltravoxMessage
*/
public void setPayload(String payload)
{
//TODO: implement payload, along with trailing 0x00 byte and update overall payload size field.
setPayload(payload.getBytes());
}
private void setPayloadLength(int length)
public void setPayload(byte[] payload)
{
//TODO: correctly size the message to payload + 1 byte and set the payload length field
int messageBitLength = (7 + payload.length) * 8;
mMessage.setSize(messageBitLength);
mMessage.setInt(payload.length, PAYLOAD_LENGTH);
int payloadPointer = 48;
for(byte payloadByte: payload)
{
mMessage.setByte(payloadPointer, payloadByte);
payloadPointer += 8;
}
mMessage.setByte(payloadPointer, (byte)0x00);
}
public boolean isValidResponse()
{
String payload = getPayload();
return payload != null && payload.startsWith(VALID_RESPONSE_PREFIX);
}
public boolean isErrorResponse()
{
String payload = getPayload();
return payload != null && payload.startsWith(ERROR_RESPONSE_PREFIX);
}
public String getError()
{
String payload = getPayload();
if(payload != null)
{
return payload.replace(ERROR_RESPONSE_PREFIX, "");
}
return null;
}
}

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2;
package audio.broadcast.shoutcast.v2.message;
public enum UltravoxMessageClass
{

View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class UltravoxMessageFactory
{
/**
* Creates an empty message of the specified message type for sending to the server
*/
public static UltravoxMessage getMessage(UltravoxMessageType type)
{
switch(type)
{
case AUTHENTICATE_BROADCAST:
return new AuthenticateBroadcast();
case CONFIGURE_ICY_GENRE:
return new ConfigureIcyGenre();
case CONFIGURE_ICY_NAME:
return new ConfigureIcyName();
case CONFIGURE_ICY_PUBLIC:
return new ConfigureIcyPublic();
case CONFIGURE_ICY_URL:
return new ConfigureIcyURL();
case FLUSH_CACHED_METADATA:
return new FlushCachedMetadata();
case MP3_DATA:
return new MP3Audio();
case NEGOTIATE_BUFFER_SIZE:
return new NegotiateBufferSize();
case NEGOTIATE_MAX_PAYLOAD_SIZE:
return new NegotiateMaxPayloadSize();
case REQUEST_CIPHER:
return new RequestCipher();
case SETUP_BROADCAST:
return new SetupBroadcast();
case STANDBY:
return new Standby();
case STREAM_MIME_TYPE:
return new StreamMimeType();
case TERMINATE_BROADCAST:
return new TerminateBroadcast();
case XML_METADATA:
return new XMLMetadata();
default:
return null;
}
}
/**
* Creates an ultravox message from the server response bytes.
*
* @param bytes in network order (big-endian).
*
* @return constructed message or null if the message type cannot be determined or is unrecognized
*/
public static UltravoxMessage getMessage(byte[] bytes)
{
UltravoxMessageType type = getMessageType(bytes);
switch(type)
{
case AUTHENTICATE_BROADCAST:
return new AuthenticateBroadcast(bytes);
case CONFIGURE_ICY_GENRE:
return new ConfigureIcyGenre(bytes);
case CONFIGURE_ICY_NAME:
return new ConfigureIcyName(bytes);
case CONFIGURE_ICY_PUBLIC:
return new ConfigureIcyPublic(bytes);
case CONFIGURE_ICY_URL:
return new ConfigureIcyURL(bytes);
case FLUSH_CACHED_METADATA:
return new FlushCachedMetadata(bytes);
case MP3_DATA:
return new MP3Audio(bytes);
case NEGOTIATE_BUFFER_SIZE:
return new NegotiateBufferSize(bytes);
case NEGOTIATE_MAX_PAYLOAD_SIZE:
return new NegotiateMaxPayloadSize(bytes);
case REQUEST_CIPHER:
return new RequestCipher(bytes);
case SETUP_BROADCAST:
return new SetupBroadcast(bytes);
case STANDBY:
return new Standby(bytes);
case STREAM_MIME_TYPE:
return new StreamMimeType(bytes);
case TERMINATE_BROADCAST:
return new TerminateBroadcast(bytes);
case XML_METADATA:
return new XMLMetadata(bytes);
case UNKNOWN:
default:
return null;
}
}
/**
* Identifies the message type from the server response byte array
*
* @param data in little-endian format
* @return message type or UNKNOWN if the message type cannot be determined.
*/
private static UltravoxMessageType getMessageType(byte[] data)
{
if(data != null && data.length > 4)
{
int value = (data[2] << 8) + data[3];
return UltravoxMessageType.fromValue(value);
}
return UltravoxMessageType.UNKNOWN;
}
}

View File

@@ -0,0 +1,90 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public enum UltravoxMessageType
{
AUTHENTICATE_BROADCAST(0x1001),
SETUP_BROADCAST(0x1002),
NEGOTIATE_BUFFER_SIZE(0x1003),
STANDBY(0x1004),
TERMINATE_BROADCAST(0x1005),
FLUSH_CACHED_METADATA(0x1006),
NEGOTIATE_MAX_PAYLOAD_SIZE(0x1008),
REQUEST_CIPHER(0x1009),
STREAM_MIME_TYPE(0x1040),
CONFIGURE_ICY_NAME(0x1100),
CONFIGURE_ICY_GENRE(0x1101),
CONFIGURE_ICY_URL(0x1102),
CONFIGURE_ICY_PUBLIC(0x1103),
XML_METADATA(0x3902),
MP3_DATA(0x7000),
UNKNOWN(0x0000);
private int mValue;
private UltravoxMessageType(int value)
{
mValue = value;
}
public int getValue()
{
return mValue;
}
public static UltravoxMessageType fromValue(int value)
{
switch(value)
{
case 0x1001:
return AUTHENTICATE_BROADCAST;
case 0x1002:
return SETUP_BROADCAST;
case 0x1003:
return NEGOTIATE_BUFFER_SIZE;
case 0x1004:
return STANDBY;
case 0x1005:
return TERMINATE_BROADCAST;
case 0x1006:
return FLUSH_CACHED_METADATA;
case 0x1008:
return NEGOTIATE_MAX_PAYLOAD_SIZE;
case 0x1009:
return REQUEST_CIPHER;
case 0x1040:
return STREAM_MIME_TYPE;
case 0x1100:
return CONFIGURE_ICY_NAME;
case 0x1101:
return CONFIGURE_ICY_GENRE;
case 0x1102:
return CONFIGURE_ICY_URL;
case 0x1103:
return CONFIGURE_ICY_PUBLIC;
case 0x3902:
return XML_METADATA;
case 0x7000:
return MP3_DATA;
default:
return UNKNOWN;
}
}
}

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public enum UltravoxMetadata
{
ALBUM_TITLE("TALB"),
ARTIST("TPE1"),
AUDIO_SOURCE_WEBPAGE("WOAS"),
BROADCAST_CLIENT_APPLICATION("TENC"),
COMMENT("COMM"),
CUSTOM_TEXT_FIELD("TXXX"),
ENCODE_SETTINGS("TSSE"),
GENERAL_BINARY_OBJECT("GEOB"),
GENRE("TCON"),
OWNER("TOWN"),
RADIO_STATION("TRSN"),
RADIO_STATION_OWNER("TRSO"),
RADIO_STATION_WEBPAGE("WORS"),
RECORDING_TIME("TDRC"),
SOURCE_MEDIA_TYPE("TMED"),
TITLE_1("TIT1"),
TITLE_2("TIT2"),
TITLE_3("TIT3"),
URL("URL"),
UNKNOWN("UNKNOWN");
private String mXMLTag;
private UltravoxMetadata(String xmlTag)
{
mXMLTag = xmlTag;
}
public String getXMLTag()
{
return mXMLTag;
}
/**
* Encodes the value as an XML tag stream
* @param value to enclose in XML tags
* @return xml tag wrapped value
*/
public String asXML(String value)
{
StringBuilder sb = new StringBuilder();
sb.append("<").append(getXMLTag()).append(">");
sb.append(value);
sb.append("</").append(getXMLTag()).append(">");
return sb.toString();
}
}

View File

@@ -0,0 +1,106 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.shoutcast.v2.message;
public class XMLMetadata extends UltravoxMessage
{
/**
* Client request to server to send cacheable XML metadata
*
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*/
XMLMetadata()
{
super(UltravoxMessageType.XML_METADATA);
}
/**
* Server response to client request
* Package private constructor. Use the UltravoxMessageFactory for this constructor.
*
* @param data bytes received from the server
*/
XMLMetadata(byte[] data)
{
super(data);
}
// /**
// * Adds custom metadata (TXXX) tagged value to the metadata set. The metadata value is identified by the
// * id parameter.
// *
// * @param id tag to identify the type of metadata
// * @param value for the id tag
// */
// public void addCustomMetadata(String id, String value)
// {
// StringBuilder sb = new StringBuilder();
// sb.append("<").append(UltravoxMetadata.CUSTOM_TEXT_FIELD.getXMLTag());
// sb.append(" id=\"").append(id).append("\">");
// sb.append(value);
// sb.append("</").append(UltravoxMetadata.CUSTOM_TEXT_FIELD.getXMLTag()).append(">");
//
// mMetadata.add(sb.toString());
// }
//
// /**
// * Adds the binary object data to the metadata set.
// * @param id tag to identify the binary object
// * @param mime type
// * @param filename associated with the data
// * @param base64Data base-64 encoded binary data to embed in the metadata tag
// */
// public void addBinaryObject(String id, String mime, String filename, String base64Data)
// {
// StringBuilder sb = new StringBuilder();
// sb.append("<").append(UltravoxMetadata.GENERAL_BINARY_OBJECT.getXMLTag());
// sb.append(" mime=\"").append(mime).append("\"");
// sb.append(" filename=\"").append(filename).append("\"");
// sb.append(" id=\"").append(id).append("\"");
// sb.append(">");
// sb.append(base64Data);
// sb.append("</").append(UltravoxMetadata.GENERAL_BINARY_OBJECT.getXMLTag()).append(">");
//
// mMetadata.add(sb.toString());
// }
//
// /**
// * Adds a recording time (TDRC) tagged metadata timestamp
// *
// * @param timestamp for the recording time
// */
// public void addRecordingTime(long timestamp)
// {
// Calendar calendar = new GregorianCalendar();
// calendar.setTimeInMillis(timestamp);
//
// StringBuilder sb = new StringBuilder();
// sb.append("<TDRC>\n");
// sb.append("<YEAR>").append(calendar.get(Calendar.YEAR)).append("</YEAR>\n");
// sb.append("<MONTH>").append(calendar.get(Calendar.MONTH)).append("</MONTH>\n");
// sb.append("<DAY>").append(calendar.get(Calendar.DAY_OF_MONTH)).append("</DAY>\n");
// sb.append("<HOUR>").append(calendar.get(Calendar.HOUR_OF_DAY)).append("</HOUR>\n");
// sb.append("<MINUTE>").append(calendar.get(Calendar.MINUTE)).append("</MINUTE>\n");
// sb.append("<ZONE>").append(calendar.get(Calendar.ZONE_OFFSET)).append("</ZONE>\n");
// sb.append("</TDRC>\n");
//
// mMetadata.add(sb.toString());
// }
}

View File

@@ -17,6 +17,7 @@
******************************************************************************/
package bits;
import java.util.Arrays;
import java.util.BitSet;
import java.util.logging.LogManager;
@@ -283,6 +284,33 @@ public class BinaryMessage extends BitSet
return values;
}
/**
* Returns this message as a little endian byte array. Extra 0 bits will be padded to the end to make the overall
* length a multiple of 8.
* @return little endian message byte array
*/
public byte[] toByteArray()
{
int length = size() / 8;
if(length * 8 < size())
{
length ++;
}
byte[] bytes = new byte[length];
int pointer = 0;
for(int x = 0; x < length; x++)
{
bytes[x] = getByte(pointer);
pointer += 8;
}
return bytes;
}
/**
* Returns this bitset as a reversed bit order array of integer ones and zeros
@@ -421,6 +449,57 @@ public class BinaryMessage extends BitSet
return (byte)( value & 0xFF );
}
/**
* Returns the byte value contained between index and index + 7 bit positions
* @param index specifying the start of the byte value
* @return byte value contained at index <> index + 7 bit positions
*/
public byte getByte(int index)
{
assert((index + 7) <= size());
int value = 0;
for(int x = 0; x < 8; x++)
{
value = value << 1;
if(get(index + x))
{
value++;
}
}
return (byte)value;
}
/**
* Sets the byte value at index position through index + 7 position.
*
* @param index bit position where to write the MSB of the byte value
* @param value to write at the index through index + 7 bit positions
*/
public void setByte(int index, byte value)
{
assert((index + 8) <= size());
int mask = 0x80;
for(int x = 0; x < 8; x++)
{
if((mask & value) == mask)
{
set(index + x);
}
else
{
clear(index + x);
}
mask = mask >> 1;
}
}
/**
* Returns the long value represented by the bit array
* @param bits - an array of bit positions that will be treated as if they

184
src/util/XTEA.java Normal file
View File

@@ -0,0 +1,184 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package util;
import java.util.Arrays;
public class XTEA
{
//Key Schedule = (sqrt(5) ­ 1) * 2^31
private static final int K = 0x9E3779B9;
//Byte mask values
private static final int MASK0 = 0xFF;
private static final int MASK1 = 0xFF00;
private static final int MASK2 = 0xFF0000;
private static final int MASK3 = 0xFF000000;
//Pre-calculated sub keys derived from encryption key
private int mSubKeys1[] = new int[32];
private int mSubKeys2[] = new int[32];
/**
* XTEA block cipher encryption using 128 bit encryption key and 64 bit plain text block size with 32 Feistel rounds.
* Shorter keys and/or plaintext values can be zero padded to the specified lengths.
*
* Algorithm source code adapted from: https://people.rit.edu/rab3106/CryptoReport.pdf (as accessed Sep. 2016)
*
* The encryption key can be changed at any point by invoking the setKey() method.
*
* @param key for encryption
*/
public XTEA(String key)
{
setKey(Arrays.copyOf(key.getBytes(), 16));
}
/**
* Set the key for this block cipher. Key must be 128 bits or 16 bytes. The key value can be zero padded to
* achieve the specified key length.
*
* @param key encryption key to use
*/
public void setKey(byte[] key)
{
assert(key.length == 16);
int keyInts[] = new int[8];
//Convert the key to an array of integers
for (int i = 0; i < 16; i += 4)
{
keyInts[i / 4] = ((key[i + 3] << 0)) & MASK0 | ((key[i + 2]) << 8) & MASK1 |
((key[i + 1]) << 16) & MASK2 | ((key[i + 0]) << 24) & MASK3;
}
//Calculate subkeys for each Feistel round
int sum, cycle;
for (sum = 0, cycle = 0; cycle < 32; cycle++)
{
//Calculate round 1 subkey
mSubKeys1[cycle] = keyInts[sum & 3];
//Add K to sum
sum += K;
//Calculate round 2 subkey
mSubKeys2[cycle] = keyInts[(sum >>> 11) & 3];
}
}
/**
* Encrypts the plaintext argument. Textual byte array must be 64 bits or 8 bytes in length and can be zero padded
* to achieve the specified length. Encrypted text byte array replaces the original plaintext byte array.
*
* @param plainText to encrypt
* @return cipher text
*/
public byte[] encrypt(byte[] plainText)
{
assert(plainText.length == 8);
//Convert the 8 bytes of plaintext to 2 integers
int result0 = (plainText[3] << 0) & MASK0 | (plainText[2] << 8) & MASK1 |
(plainText[1] << 16) & MASK2 | (plainText[0] << 24) & MASK3;
int result1 = (plainText[7] << 0) & MASK0 | (plainText[6] << 8) & MASK1 |
(plainText[5] << 16) & MASK2 | (plainText[4] << 24) & MASK3;
//Unrolled loop for all Feistel rounds
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 0) + mSubKeys1[0];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 1) + mSubKeys2[0];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 1) + mSubKeys1[1];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 2) + mSubKeys2[1];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 2) + mSubKeys1[2];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 3) + mSubKeys2[2];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 3) + mSubKeys1[3];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 4) + mSubKeys2[3];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 4) + mSubKeys1[4];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 5) + mSubKeys2[4];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 5) + mSubKeys1[5];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 6) + mSubKeys2[5];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 6) + mSubKeys1[6];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 7) + mSubKeys2[6];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 7) + mSubKeys1[7];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 8) + mSubKeys2[7];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 8) + mSubKeys1[8];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 9) + mSubKeys2[8];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 9) + mSubKeys1[9];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 10) + mSubKeys2[9];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 10) + mSubKeys1[10];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 11) + mSubKeys2[10];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 11) + mSubKeys1[11];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 12) + mSubKeys2[11];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 12) + mSubKeys1[12];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 13) + mSubKeys2[12];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 13) + mSubKeys1[13];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 14) + mSubKeys2[13];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 14) + mSubKeys1[14];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 15) + mSubKeys2[14];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 15) + mSubKeys1[15];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 16) + mSubKeys2[15];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 16) + mSubKeys1[16];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 17) + mSubKeys2[16];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 17) + mSubKeys1[17];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 18) + mSubKeys2[17];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 18) + mSubKeys1[18];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 19) + mSubKeys2[18];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 19) + mSubKeys1[19];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 20) + mSubKeys2[19];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 20) + mSubKeys1[20];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 21) + mSubKeys2[20];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 21) + mSubKeys1[21];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 22) + mSubKeys2[21];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 22) + mSubKeys1[22];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 23) + mSubKeys2[22];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 23) + mSubKeys1[23];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 24) + mSubKeys2[23];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 24) + mSubKeys1[24];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 25) + mSubKeys2[24];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 25) + mSubKeys1[25];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 26) + mSubKeys2[25];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 26) + mSubKeys1[26];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 27) + mSubKeys2[26];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 27) + mSubKeys1[27];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 28) + mSubKeys2[27];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 28) + mSubKeys1[28];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 29) + mSubKeys2[28];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 29) + mSubKeys1[29];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 30) + mSubKeys2[29];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 30) + mSubKeys1[30];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 31) + mSubKeys2[30];
result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 31) + mSubKeys1[31];
result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 32) + mSubKeys2[31];
byte[] cipherText = new byte[8];
cipherText[0] = (byte)(result0 >>> 24);
cipherText[1] = (byte)(result0 >>> 16);
cipherText[2] = (byte)(result0 >>> 8);
cipherText[3] = (byte)(result0 >>> 0);
cipherText[4] = (byte)(result1 >>> 24);
cipherText[5] = (byte)(result1 >>> 16);
cipherText[6] = (byte)(result1 >>> 8);
cipherText[7] = (byte)(result1 >>> 0);
return cipherText;
}
}