4343public 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
0 commit comments