Skip to content

Commit 83d3863

Browse files
committed
Implemented reconnection (closes #43)
1 parent e47df13 commit 83d3863

2 files changed

Lines changed: 81 additions & 38 deletions

File tree

core/src/main/java/xyz/gianlu/librespot/core/Session.java

Lines changed: 77 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,9 @@
4343
public class Session implements Closeable {
4444
private static final Logger LOGGER = Logger.getLogger(Session.class);
4545
private final DiffieHellman keys;
46-
private final Socket socket;
47-
private final DataInputStream in;
48-
private final DataOutputStream out;
4946
private final Inner inner;
5047
private final ExecutorService executorService = Executors.newCachedThreadPool(new NameThreadFactory(r -> "handle-packet-" + r.hashCode()));
48+
private ConnectionHolder conn;
5149
private CipherPair cipherPair;
5250
private Receiver receiver;
5351
private Authentication.APWelcome apWelcome = null;
@@ -59,11 +57,8 @@ public class Session implements Closeable {
5957

6058
private Session(Inner inner, Socket socket) throws IOException {
6159
this.inner = inner;
62-
this.socket = socket;
6360
this.keys = new DiffieHellman(inner.random);
64-
65-
this.in = new DataInputStream(socket.getInputStream());
66-
this.out = new DataOutputStream(socket.getOutputStream());
61+
this.conn = new ConnectionHolder(socket);
6762

6863
LOGGER.info(String.format("Created new session! {deviceId: %s, ap: %s} ", inner.deviceId, socket.getInetAddress()));
6964
}
@@ -107,11 +102,11 @@ void connect() throws IOException, GeneralSecurityException, SpotifyAuthenticati
107102

108103
byte[] clientHelloBytes = clientHello.toByteArray();
109104
int length = 2 + 4 + clientHelloBytes.length;
110-
out.writeByte(0);
111-
out.writeByte(4);
112-
out.writeInt(length);
113-
out.write(clientHelloBytes);
114-
out.flush();
105+
conn.out.writeByte(0);
106+
conn.out.writeByte(4);
107+
conn.out.writeInt(length);
108+
conn.out.write(clientHelloBytes);
109+
conn.out.flush();
115110

116111
acc.writeByte(0);
117112
acc.writeByte(4);
@@ -121,10 +116,10 @@ void connect() throws IOException, GeneralSecurityException, SpotifyAuthenticati
121116

122117
// Read APResponseMessage
123118

124-
length = in.readInt();
119+
length = conn.in.readInt();
125120
acc.writeInt(length);
126121
byte[] buffer = new byte[length - 4];
127-
in.readFully(buffer);
122+
conn.in.readFully(buffer);
128123
acc.write(buffer);
129124
acc.dump();
130125

@@ -165,26 +160,26 @@ void connect() throws IOException, GeneralSecurityException, SpotifyAuthenticati
165160

166161
byte[] clientResponsePlaintextBytes = clientResponsePlaintext.toByteArray();
167162
length = 4 + clientResponsePlaintextBytes.length;
168-
out.writeInt(length);
169-
out.write(clientResponsePlaintextBytes);
170-
out.flush();
163+
conn.out.writeInt(length);
164+
conn.out.write(clientResponsePlaintextBytes);
165+
conn.out.flush();
171166

172167
try {
173168
byte[] scrap = new byte[4];
174-
socket.setSoTimeout((int) TimeUnit.SECONDS.toMillis(1));
175-
int read = in.read(scrap);
169+
conn.socket.setSoTimeout((int) TimeUnit.SECONDS.toMillis(1));
170+
int read = conn.in.read(scrap);
176171
if (read == scrap.length) {
177172
length = (scrap[0] << 24) | (scrap[1] << 16) | (scrap[2] << 8) | (scrap[3] & 0xFF);
178173
byte[] payload = new byte[length - 4];
179-
in.readFully(payload);
174+
conn.in.readFully(payload);
180175
Keyexchange.APLoginFailed failed = Keyexchange.APResponseMessage.parseFrom(payload).getLoginFailed();
181176
throw new SpotifyAuthenticationException(failed);
182177
} else if (read > 0) {
183178
throw new IllegalStateException("Read unknown data!");
184179
}
185180
} catch (SocketTimeoutException ignored) {
186181
} finally {
187-
socket.setSoTimeout(0);
182+
conn.socket.setSoTimeout(0);
188183
}
189184

190185

@@ -197,6 +192,20 @@ void connect() throws IOException, GeneralSecurityException, SpotifyAuthenticati
197192
}
198193

199194
void authenticate(@NotNull Authentication.LoginCredentials credentials) throws IOException, GeneralSecurityException, SpotifyAuthenticationException, MercuryClient.PubSubException, SpotifyIrc.IrcException {
195+
authenticatePartial(credentials);
196+
197+
mercuryClient = new MercuryClient(this);
198+
199+
audioKeyManager = new AudioKeyManager(this);
200+
channelManager = new ChannelManager(this);
201+
spirc = new SpotifyIrc(this);
202+
spirc.subscribe();
203+
player = new Player(inner.configuration, inner.configuration, this);
204+
205+
LOGGER.info(String.format("Authenticated as %s!", apWelcome.getCanonicalUsername()));
206+
}
207+
208+
private void authenticatePartial(@NotNull Authentication.LoginCredentials credentials) throws IOException, GeneralSecurityException, SpotifyAuthenticationException {
200209
if (cipherPair == null) throw new IllegalStateException("Connection not established!");
201210

202211
Authentication.ClientResponseEncrypted clientResponseEncrypted = Authentication.ClientResponseEncrypted.newBuilder()
@@ -212,20 +221,13 @@ void authenticate(@NotNull Authentication.LoginCredentials credentials) throws I
212221

213222
send(Packet.Type.Login, clientResponseEncrypted.toByteArray());
214223

215-
Packet packet = cipherPair.receiveEncoded(in);
224+
Packet packet = cipherPair.receiveEncoded(conn.in);
216225
if (packet.is(Packet.Type.APWelcome)) {
217226
apWelcome = Authentication.APWelcome.parseFrom(packet.payload);
218-
mercuryClient = new MercuryClient(this);
227+
219228
receiver = new Receiver();
220229
new Thread(receiver, "session-packet-receiver").start();
221230

222-
audioKeyManager = new AudioKeyManager(this);
223-
channelManager = new ChannelManager(this);
224-
spirc = new SpotifyIrc(this);
225-
player = new Player(inner.configuration, inner.configuration, this);
226-
227-
LOGGER.info(String.format("Authenticated as %s!", apWelcome.getCanonicalUsername()));
228-
229231
byte[] bytes0x0f = new byte[20];
230232
random().nextBytes(bytes0x0f);
231233
send(Packet.Type.Unknown_0x0f, bytes0x0f);
@@ -257,16 +259,16 @@ public void close() throws IOException {
257259
receiver = null;
258260

259261
executorService.shutdown();
260-
socket.close();
262+
conn.socket.close();
261263

262264
apWelcome = null;
263265
cipherPair = null;
264266

265-
LOGGER.info(String.format("Closed session. {deviceId: %s, ap: %s} ", inner.deviceId, socket.getInetAddress()));
267+
LOGGER.info(String.format("Closed session. {deviceId: %s, ap: %s} ", inner.deviceId, conn.socket.getInetAddress()));
266268
}
267269

268270
public void send(Packet.Type cmd, byte[] payload) throws IOException {
269-
cipherPair.sendEncoded(out, cmd.val, payload);
271+
cipherPair.sendEncoded(conn.out, cmd.val, payload);
270272
}
271273

272274
@NotNull
@@ -306,7 +308,7 @@ public Authentication.APWelcome apWelcome() {
306308
}
307309

308310
public boolean valid() {
309-
return apWelcome != null && !socket.isClosed();
311+
return apWelcome != null && conn != null && !conn.socket.isClosed();
310312
}
311313

312314
@NotNull
@@ -334,6 +336,29 @@ public Random random() {
334336
return inner.random;
335337
}
336338

339+
private void reconnect() {
340+
try {
341+
if (conn != null) {
342+
conn.socket.close();
343+
receiver.stop();
344+
}
345+
346+
conn = new ConnectionHolder(ApResolver.getSocketFromRandomAccessPoint());
347+
connect();
348+
authenticatePartial(Authentication.LoginCredentials.newBuilder()
349+
.setUsername(apWelcome.getCanonicalUsername())
350+
.setTyp(apWelcome.getReusableAuthCredentialsType())
351+
.setAuthData(apWelcome.getReusableAuthCredentials())
352+
.build());
353+
354+
spirc.subscribe();
355+
356+
LOGGER.info(String.format("Re-authenticated as %s!", apWelcome.getCanonicalUsername()));
357+
} catch (IOException | GeneralSecurityException | SpotifyAuthenticationException | MercuryClient.PubSubException | SpotifyIrc.IrcException ex) {
358+
throw new RuntimeException("Failed reconnecting!", ex);
359+
}
360+
}
361+
337362
public enum DeviceType {
338363
Unknown(0, "unknown"),
339364
Computer(1, "computer"),
@@ -530,6 +555,18 @@ byte[] array() {
530555
}
531556
}
532557

558+
private class ConnectionHolder {
559+
final Socket socket;
560+
final DataInputStream in;
561+
final DataOutputStream out;
562+
563+
ConnectionHolder(Socket socket) throws IOException {
564+
this.socket = socket;
565+
this.in = new DataInputStream(socket.getInputStream());
566+
this.out = new DataOutputStream(socket.getOutputStream());
567+
}
568+
}
569+
533570
private class Receiver implements Runnable {
534571
private volatile boolean shouldStop = false;
535572

@@ -546,14 +583,18 @@ public void run() {
546583
Packet packet;
547584
Packet.Type cmd;
548585
try {
549-
packet = cipherPair.receiveEncoded(in);
586+
packet = cipherPair.receiveEncoded(conn.in);
550587
cmd = Packet.Type.parse(packet.cmd);
551588
if (cmd == null) {
552589
LOGGER.info("Skipping unknown CMD 0x" + Integer.toHexString(packet.cmd));
553590
continue;
554591
}
555592
} catch (IOException | GeneralSecurityException ex) {
556-
if (!shouldStop) LOGGER.fatal("Failed reading packet!", ex);
593+
if (!shouldStop) {
594+
LOGGER.fatal("Failed reading packet!", ex);
595+
reconnect();
596+
}
597+
557598
return;
558599
}
559600

core/src/main/java/xyz/gianlu/librespot/spirc/SpotifyIrc.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@ public class SpotifyIrc implements Closeable {
2626
private final AtomicInteger seqHolder = new AtomicInteger(1);
2727
private final String uri;
2828
private final Session session;
29-
private final SpircListener internalListener;
3029
private final Spirc.DeviceState.Builder deviceState;
30+
private SpircListener internalListener;
3131

32-
public SpotifyIrc(@NotNull Session session) throws IOException, IrcException, MercuryClient.PubSubException {
32+
public SpotifyIrc(@NotNull Session session) {
3333
this.session = session;
3434
this.uri = String.format("hm://remote/user/%s/", session.apWelcome().getCanonicalUsername());
3535
this.deviceState = initializeDeviceState();
36+
}
3637

38+
public void subscribe() throws IOException, IrcException, MercuryClient.PubSubException {
3739
session.mercury().subscribe(uri, internalListener = new SpircListener());
3840

3941
send(Spirc.MessageType.kMessageTypeHello);

0 commit comments

Comments
 (0)