Skip to content

Commit

Permalink
fix rtsp reconnect
Browse files Browse the repository at this point in the history
  • Loading branch information
serezhka committed Oct 5, 2020
1 parent b80081b commit a234248
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 135 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ plugins {
}

group 'com.github.serezhka'
version '1.0.3'
version '1.0.4'

sourceCompatibility = 1.9
sourceCompatibility = JavaVersion.VERSION_1_9

repositories {
mavenCentral()
Expand Down
38 changes: 33 additions & 5 deletions src/main/java/com/github/serezhka/jap2lib/AirPlay.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.github.serezhka.jap2lib;

import com.github.serezhka.jap2lib.rtsp.MediaStreamInfo;

import java.io.InputStream;
import java.io.OutputStream;

Expand Down Expand Up @@ -66,13 +68,39 @@ public void fairPlaySetup(InputStream in, OutputStream out) throws Exception {
}

/**
* {@code RTSP SETUP}
* Retrieves information about media stream from RTSP SETUP request
*
* @return null if there's no stream info
*/
public MediaStreamInfo rtspGetMediaStreamInfo(InputStream in) throws Exception {
return rtsp.getMediaStreamInfo(in);
}

/**
* {@code RTSP SETUP ENCRYPTION}
* <p>
* Retrieves encrypted EAS key and IV
*/
public void rtspSetupEncryption(InputStream in) throws Exception {
rtsp.setup(in);
}

/**
* {@code RTSP SETUP VIDEO}
* <p>
* Writes video event, data and timing ports info to output stream
*/
public void rtspSetupVideo(OutputStream out, int videoDataPort, int videoEventPort, int videoTimingPort) throws Exception {
rtsp.setupVideo(out, videoDataPort, videoEventPort, videoTimingPort);
}

/**
* {@code RTSP SETUP AUDIO}
* <p>
* Writes RSTP SETUP response bytes to output stream, returns stream data type: 110 - video, 96 - audio, 0 - no stream assigned
* Writes audio control and data ports info to output stream
*/
public void rtspSetup(InputStream in, OutputStream out,
int videoDataPort, int videoEventPort, int videoTimingPort, int audioDataPort, int audioControlPort) throws Exception {
rtsp.rtspSetup(in, out, videoDataPort, videoEventPort, videoTimingPort, audioDataPort, audioControlPort);
public void rtspSetupAudio(OutputStream out, int audioDataPort, int audioControlPort) throws Exception {
rtsp.setupAudio(out, audioDataPort, audioControlPort);
}

public byte[] getFairPlayAesKey() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/github/serezhka/jap2lib/ModifiedMD5.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class ModifiedMD5 {

private int[] shift = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
private final int[] shift = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/github/serezhka/jap2lib/OmgHax.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ void generate_session_key(byte[] oldSap, byte[] messageIn, byte[] sessionKey) {
}
}

void cycle(byte[] block, int key_schedule[][]) {
void cycle(byte[] block, int[][] key_schedule) {
int ptr1, ptr2, ptr3, ptr4, ab;

ByteBuffer bWords = ByteBuffer.wrap(block);
Expand Down
64 changes: 7 additions & 57 deletions src/main/java/com/github/serezhka/jap2lib/Pairing.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.serezhka.jap2lib;

import com.dd.plist.BinaryPropertyListWriter;
import com.dd.plist.NSArray;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
import com.dd.plist.PropertyListParser;
import net.i2p.crypto.eddsa.EdDSAEngine;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.KeyPairGenerator;
Expand All @@ -23,6 +23,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
Expand All @@ -44,59 +45,9 @@ class Pairing {
this.keyPair = new KeyPairGenerator().generateKeyPair();
}

void info(OutputStream out) throws IOException {
NSArray audioFormats = new NSArray(2);
NSDictionary audioFormat1 = new NSDictionary();
audioFormat1.put("audioInputFormats", 67108860);
audioFormat1.put("audioOutputFormats", 67108860);
audioFormat1.put("type", 100);
audioFormats.setValue(0, audioFormat1);
NSDictionary audioFormat2 = new NSDictionary();
audioFormat2.put("audioInputFormats", 67108860);
audioFormat2.put("audioOutputFormats", 67108860);
audioFormat2.put("type", 101);
audioFormats.setValue(1, audioFormat2);

NSArray audioLatencies = new NSArray(2);
NSDictionary audioLatency1 = new NSDictionary();
audioLatency1.put("audioType", "default");
audioLatency1.put("inputLatencyMicros", false);
audioLatency1.put("type", 100);
audioLatencies.setValue(0, audioLatency1);
NSDictionary audioLatency2 = new NSDictionary();
audioLatency2.put("audioType", "default");
audioLatency2.put("inputLatencyMicros", false);
audioLatency2.put("type", 101);
audioLatencies.setValue(1, audioLatency2);

NSArray displays = new NSArray(1);
NSDictionary display = new NSDictionary();
display.put("features", 14);
display.put("height", 1080);
display.put("heightPhysical", false);
display.put("heightPixels", 1080);
display.put("maxFPS", 30);
display.put("overscanned", false);
display.put("refreshRate", 60);
display.put("rotation", false);
display.put("uuid", "e5f7a68d-7b0f-4305-984b-974f677a150b");
display.put("width", 1920);
display.put("widthPhysical", false);
display.put("widthPixels", 1920);
displays.setValue(0, display);

NSDictionary serverInfo = new NSDictionary();
serverInfo.put("audioFormats", audioFormats);
serverInfo.put("audioLatencies", audioLatencies);
serverInfo.put("displays", displays);
serverInfo.put("features", 130367356919L);
serverInfo.put("keepAliveSendStatsAsBody", 1);
serverInfo.put("model", "AppleTV2,1");
serverInfo.put("name", "Apple TV");
serverInfo.put("pi", "b08f5a79-db29-4384-b456-a4784d9e6055");
serverInfo.put("sourceVersion", "220.68");
serverInfo.put("statusFlags", 68);
serverInfo.put("vv", 2);
void info(OutputStream out) throws Exception {
URL response = Pairing.class.getResource("/info-response.xml");
NSObject serverInfo = PropertyListParser.parse(response.openStream());
BinaryPropertyListWriter.write(out, serverInfo);
}

Expand All @@ -107,8 +58,8 @@ void pairSetup(OutputStream out) throws IOException {
@SuppressWarnings("ResultOfMethodCallIgnored")
void pairVerify(InputStream request, OutputStream response) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, SignatureException, BadPaddingException, IllegalBlockSizeException, IOException {
int flag = request.read();
request.skip(3);
if (flag > 0) {
request.skip(3);
request.read(ecdhTheirs = new byte[32]);
request.read(edTheirs = new byte[32]);

Expand Down Expand Up @@ -137,7 +88,6 @@ void pairVerify(InputStream request, OutputStream response) throws NoSuchAlgorit

response.write(responseContent);
} else {
request.skip(3);
byte[] signature = new byte[64];
request.read(signature);

Expand Down
132 changes: 65 additions & 67 deletions src/main/java/com/github/serezhka/jap2lib/RTSP.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
import com.dd.plist.BinaryPropertyListWriter;
import com.dd.plist.NSArray;
import com.dd.plist.NSDictionary;
import com.github.serezhka.jap2lib.rtsp.AudioStreamInfo;
import com.github.serezhka.jap2lib.rtsp.MediaStreamInfo;
import com.github.serezhka.jap2lib.rtsp.VideoStreamInfo;
import net.i2p.crypto.eddsa.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
Expand All @@ -19,67 +24,78 @@ class RTSP {
private byte[] encryptedAESKey;
private byte[] eiv;

void rtspSetup(InputStream in, OutputStream out,
int videoDataPort, int videoEventPort, int videoTimingPort, int audioDataPort, int audioControlPort) throws Exception {
NSDictionary request = (NSDictionary) BinaryPropertyListParser.parse(in);

log.debug("Binary property list parsed:\n{}", request.toXMLPropertyList());
MediaStreamInfo getMediaStreamInfo(InputStream rtspSetupPayload) throws Exception {
NSDictionary rtspSetup = (NSDictionary) BinaryPropertyListParser.parse(rtspSetupPayload);

if (request.containsKey("ekey")) {
encryptedAESKey = (byte[]) request.get("ekey").toJavaObject();
}

if (request.containsKey("eiv")) {
eiv = (byte[]) request.get("eiv").toJavaObject();
}

if (request.containsKey("streams")) {
HashMap stream = (HashMap) ((Object[]) request.get("streams").toJavaObject())[0]; // iter
log.debug("Binary property list parsed:\n{}", rtspSetup.toXMLPropertyList());

if (rtspSetup.containsKey("streams")) {
// assume one stream info per RTSP SETUP request
HashMap stream = (HashMap) ((Object[]) rtspSetup.get("streams").toJavaObject())[0];
int type = (int) stream.get("type");

switch (type) {

// mirror
case 110: {
streamConnectionID = Long.toUnsignedString((long) stream.get("streamConnectionID"));

NSArray streams = new NSArray(1);
NSDictionary dataStream = new NSDictionary();
dataStream.put("dataPort", videoDataPort);
dataStream.put("type", 110);
streams.setValue(0, dataStream);

NSDictionary response = new NSDictionary();
response.put("streams", streams);
response.put("eventPort", videoEventPort);
response.put("timingPort", videoTimingPort);
BinaryPropertyListWriter.write(out, response);
break;
}
// video
case 110:
if (stream.containsKey("streamConnectionID")) {
streamConnectionID = Long.toUnsignedString((long) stream.get("streamConnectionID"));
}
return new VideoStreamInfo(streamConnectionID);

// audio
case 96: {

log.debug("Audio format: {}", getAudioFormatDescription((int) stream.get("audioFormat")));

NSArray streams = new NSArray(1);
NSDictionary dataStream = new NSDictionary();
dataStream.put("dataPort", audioDataPort);
dataStream.put("type", 96);
dataStream.put("controlPort", audioControlPort);
streams.setValue(0, dataStream);

NSDictionary response = new NSDictionary();
response.put("streams", streams);
BinaryPropertyListWriter.write(out, response);
break;
}
case 96:
if (stream.containsKey("audioFormat")) {
long audioFormatCode = (int) stream.get("audioFormat"); // FIXME int or long ?!
return new AudioStreamInfo(audioFormatCode);
}
return new AudioStreamInfo();

default:
log.warn("Unknown stream type: {}", type);
}
}
return null;
}

void setup(InputStream in) throws Exception {
NSDictionary request = (NSDictionary) BinaryPropertyListParser.parse(in);

if (request.containsKey("ekey")) {
encryptedAESKey = (byte[]) request.get("ekey").toJavaObject();
log.info("Encrypted AES key: " + Utils.bytesToHex(encryptedAESKey));
}

if (request.containsKey("eiv")) {
eiv = (byte[]) request.get("eiv").toJavaObject();
log.info("AES eiv: " + Utils.bytesToHex(eiv));
}
}

void setupVideo(OutputStream out, int videoDataPort, int videoEventPort, int videoTimingPort) throws IOException {
NSArray streams = new NSArray(1);
NSDictionary dataStream = new NSDictionary();
dataStream.put("dataPort", videoDataPort);
dataStream.put("type", 110);
streams.setValue(0, dataStream);

NSDictionary response = new NSDictionary();
response.put("streams", streams);
response.put("eventPort", videoEventPort);
response.put("timingPort", videoTimingPort);
BinaryPropertyListWriter.write(out, response);
}

void setupAudio(OutputStream out, int audioDataPort, int audioControlPort) throws IOException {
NSArray streams = new NSArray(1);
NSDictionary dataStream = new NSDictionary();
dataStream.put("dataPort", audioDataPort);
dataStream.put("type", 96);
dataStream.put("controlPort", audioControlPort);
streams.setValue(0, dataStream);

NSDictionary response = new NSDictionary();
response.put("streams", streams);
BinaryPropertyListWriter.write(out, response);
}

String getStreamConnectionID() {
Expand All @@ -93,22 +109,4 @@ byte[] getEncryptedAESKey() {
byte[] getEiv() {
return eiv;
}

private String getAudioFormatDescription(int format) {
String formatDescription;
switch (format) {
case 0x40000:
formatDescription = "96 AppleLossless, 96 352 0 16 40 10 14 2 255 0 0 44100";
break;
case 0x400000:
formatDescription = "96 mpeg4-generic/44100/2, 96 mode=AAC-main; constantDuration=1024";
break;
case 0x1000000:
formatDescription = "96 mpeg4-generic/44100/2, 96 mode=AAC-eld; constantDuration=480";
break;
default:
formatDescription = "Unknown: " + format;
}
return formatDescription;
}
}
Loading

0 comments on commit a234248

Please sign in to comment.