11package net .sharksystem .asap .android .service ;
22
33import android .content .Context ;
4+ import android .net .wifi .WifiInfo ;
5+ import android .net .wifi .WifiManager ;
46import android .util .Log ;
57
68import net .sharksystem .asap .ASAPException ;
79import net .sharksystem .asap .protocol .ASAPConnection ;
810
11+ import java .io .DataInputStream ;
12+ import java .io .DataOutputStream ;
913import java .io .IOException ;
1014import java .io .InputStream ;
1115import java .io .OutputStream ;
1216import java .util .Date ;
1317import java .util .HashMap ;
1418import java .util .Map ;
19+ import java .util .Random ;
1520import java .util .UUID ;
1621
1722public abstract class MacLayerEngine {
@@ -25,6 +30,7 @@ public abstract class MacLayerEngine {
2530// public static final long DEFAULT_WAIT_BEFORE_RECONNECT_TIME = 1000*60; // a minute
2631 public static final long DEFAULT_WAIT_BEFORE_RECONNECT_TIME = 1000 ; // a second - debugging
2732 private final long waitBeforeReconnect ;
33+ private final int randomValue ; // to avoid a nasty race condition
2834
2935 public MacLayerEngine (ASAPService asapService , Context context ) {
3036 this (asapService , context , DEFAULT_WAIT_BEFORE_RECONNECT_TIME );
@@ -34,6 +40,9 @@ public MacLayerEngine(ASAPService asapService, Context context, long waitingPeri
3440 this .asapService = asapService ;
3541 this .context = context ;
3642 this .waitBeforeReconnect = waitingPeriod ;
43+
44+ Random random = new Random (System .currentTimeMillis ());
45+ this .randomValue = random .nextInt ();
3746 }
3847
3948 protected Context getContext () {
@@ -111,6 +120,19 @@ private String getLogStart() {
111120
112121 private Map <String , ASAPConnection > asapConnections = new HashMap <>();
113122
123+ private String localMacAddress = null ;
124+ public String getLocalMacAddress () {
125+ if (localMacAddress == null ) {
126+ WifiManager wifiManager = (WifiManager )
127+ asapService .getApplicationContext ().getSystemService (Context .WIFI_SERVICE );
128+
129+ WifiInfo wInfo = wifiManager .getConnectionInfo ();
130+ this .localMacAddress = wInfo .getMacAddress ();
131+ }
132+
133+ return this .localMacAddress ;
134+ }
135+
114136 /**
115137 * kill connection to address
116138 * @param address
@@ -142,5 +164,80 @@ protected void launchASAPConnection(
142164 }
143165 }
144166
167+ /** There is a race condition which is not that obvious: Example Bluetooth
168+ Bot phones A and B are going to initiate a connection. Both also offer a server socket.
169+ Assumed, A and B initiate a connection in the same moment (create a client socket).
170+ Both would get a connection and launch an ASAP session. If the timing is bad - and that's
171+ more likely as I wished - both would be asked from their server sockets to handle a
172+ new connection as well.
173+
174+ In principle, that is what we want. With a bad timing that would happen on both sides, though.
175+ In that case, both would realize that there is already an existing communication channel
176+ (their own TCP client socket) and close the server side socket. Again, that is what we
177+ want - but not on both ends.
178+
179+ Both connections would be killed. We want one TCP channel to be closed. But only one.
180+
181+ Solution: We implement a bias: This method is called with an additional parameter (initiator).
182+ In TCP, we could call it client socket. Connection was initiated by creating a TCP port. It can be used
183+ in any way but it must be ensured: Both side will not use same boolean value
184+ */
185+ public boolean waitBeforeASAPSessionLaunch (InputStream is , OutputStream os ,
186+ boolean connectionInitiator , long waitInMillis ) throws IOException {
187+ // run a little negotiation before we start
188+ DataOutputStream dos = new DataOutputStream (os );
189+ int remoteValue = 0 ;
190+
191+ try {
192+ dos .writeInt (this .randomValue );
193+ DataInputStream dis = new DataInputStream (is );
194+ remoteValue = dis .readInt ();
195+ } catch (IOException e ) {
196+ // decision is made - this connection is gone anyway
197+ os .close ();
198+ is .close ();
199+ }
200+
201+ StringBuilder sb = new StringBuilder ();
202+ sb .append ("try to solve race condition: localValue == " );
203+ sb .append (this .randomValue );
204+ sb .append (" | remoteValue == " );
205+ sb .append (remoteValue );
206+ sb .append (" | initiator == " );
207+ sb .append (connectionInitiator );
208+
209+ int initiatorValue , nonInitiatorValue ;
210+ if (connectionInitiator ) {
211+ initiatorValue = this .randomValue ;
212+ nonInitiatorValue = remoteValue ;
213+ } else {
214+ initiatorValue = remoteValue ;
215+ nonInitiatorValue = this .randomValue ;
216+ }
217+
218+ sb .append (" | initiatorValue == " );
219+ sb .append (initiatorValue );
220+ sb .append (" | nonInitiatorValue == " );
221+ sb .append (nonInitiatorValue );
222+ Log .d (this .getLogStart (), sb .toString ());
223+
224+ /* Here comes the bias: An initiator with a smaller value waits a moment */
225+ if (connectionInitiator & initiatorValue < nonInitiatorValue ) {
226+ try {
227+ sb = new StringBuilder ();
228+ sb .append ("wait " );
229+ sb .append (waitInMillis );
230+ sb .append (" ms" );
231+ Log .d (this .getLogStart (), sb .toString ());
232+ Thread .sleep (waitInMillis );
233+ return true ;
234+ } catch (InterruptedException e ) {
235+ Log .d (this .getLogStart (), "wait interrupted" );
236+ }
237+ }
238+
239+ return false ;
240+ }
241+
145242 public abstract void checkConnectionStatus ();
146243}
0 commit comments