SelfRecoveryTest.java revision 4e1c7af3e5b47bc8f53e92aa25047c5ca03238ce
196a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius/* 296a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * Copyright (C) 2017 The Android Open Source Project 396a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * 496a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * Licensed under the Apache License, Version 2.0 (the "License"); 596a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * you may not use this file except in compliance with the License. 696a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * You may obtain a copy of the License at 796a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * 896a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * http://www.apache.org/licenses/LICENSE-2.0 996a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * 1096a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * Unless required by applicable law or agreed to in writing, software 1196a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * distributed under the License is distributed on an "AS IS" BASIS, 1296a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1396a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * See the License for the specific language governing permissions and 1496a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * limitations under the License. 1596a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius */ 1696a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 1796a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Piuspackage com.android.server.wifi; 1896a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 1996a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Piusimport static org.mockito.Mockito.*; 2096a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Piusimport static org.mockito.MockitoAnnotations.*; 2196a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 227f2a1e30dd5d14320ffc7b185ac25fdb79fe52b0Etan Cohenimport android.support.test.filters.SmallTest; 2396a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 2496a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Piusimport org.junit.Before; 2596a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Piusimport org.junit.Test; 2696a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Piusimport org.mockito.Mock; 2796a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 2896a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius/** 2996a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * Unit tests for {@link com.android.server.wifi.SelfRecovery}. 3096a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius */ 3196a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius@SmallTest 3296a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Piuspublic class SelfRecoveryTest { 3396a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius SelfRecovery mSelfRecovery; 3496a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius @Mock WifiController mWifiController; 35e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne @Mock Clock mClock; 3696a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 3796a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius @Before 3896a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius public void setUp() throws Exception { 3996a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius initMocks(this); 40e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne mSelfRecovery = new SelfRecovery(mWifiController, mClock); 4196a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius } 4296a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 4396a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius /** 4496a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * Verifies that invocations of {@link SelfRecovery#trigger(int)} with valid reasons will send 4596a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * the restart message to {@link WifiController}. 4696a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius */ 4796a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius @Test 4896a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius public void testValidTriggerReasonsSendMessageToWifiController() { 4996a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG); 504e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), anyInt()); 5196a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius reset(mWifiController); 5296a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 53e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne when(mClock.getElapsedSinceBootMillis()) 54e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne .thenReturn(SelfRecovery.MAX_RESTARTS_TIME_WINDOW_MILLIS + 1); 5526e5eb3545a8d67c66e546f4ce413c0611573cfbRoshan Pius mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); 564e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), anyInt()); 5796a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius reset(mWifiController); 5896a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius } 5996a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 6096a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius /** 6196a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * Verifies that invocations of {@link SelfRecovery#trigger(int)} with invalid reasons will not 6296a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius * send the restart message to {@link WifiController}. 6396a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius */ 6496a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius @Test 6596a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius public void testInvalidTriggerReasonsDoesNotSendMessageToWifiController() { 6696a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius mSelfRecovery.trigger(-1); 67a5a83f137409921fe95dc01214a0307838b8508cxshu verify(mWifiController, never()).sendMessage(anyInt(), anyString()); 6896a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius 6996a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius mSelfRecovery.trigger(8); 70a5a83f137409921fe95dc01214a0307838b8508cxshu verify(mWifiController, never()).sendMessage(anyInt(), anyString()); 7196a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius } 72e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne 73e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne /** 744e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein * Verifies that a STA interface down event will trigger WifiController to disable wifi. 754e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein */ 764e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein @Test 774e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein public void testStaIfaceDownDisablesWifi() { 784e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein mSelfRecovery.trigger(SelfRecovery.REASON_STA_IFACE_DOWN); 794e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_DISABLE_WIFI)); 804e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein } 814e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein 824e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein /** 83e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne * Verifies that invocations of {@link SelfRecovery#trigger(int)} for REASON_HAL_CRASH & 84e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne * REASON_WIFICOND_CRASH are limited to {@link SelfRecovery#MAX_RESTARTS_IN_TIME_WINDOW} in a 85e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne * {@link SelfRecovery#MAX_RESTARTS_TIME_WINDOW_MILLIS} millisecond time window. 86e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne */ 87e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne @Test 88e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne public void testTimeWindowLimiting_typicalUse() { 89e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne when(mClock.getElapsedSinceBootMillis()).thenReturn(0L); 90e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne // Fill up the SelfRecovery's restart time window buffer, ensure all the restart triggers 91e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne // aren't ignored 92e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne for (int i = 0; i < SelfRecovery.MAX_RESTARTS_IN_TIME_WINDOW / 2; i++) { 9326e5eb3545a8d67c66e546f4ce413c0611573cfbRoshan Pius mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); 944e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), 954e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein anyInt()); 96e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 97e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne 9826e5eb3545a8d67c66e546f4ce413c0611573cfbRoshan Pius mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); 994e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), 1004e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein anyInt()); 101e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 102e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne } 103e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne if ((SelfRecovery.MAX_RESTARTS_IN_TIME_WINDOW % 2) == 1) { 10426e5eb3545a8d67c66e546f4ce413c0611573cfbRoshan Pius mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); 1054e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), 1064e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein anyInt()); 107e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 108e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne } 109e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne 110e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne // Verify that further attempts to trigger restarts for are ignored 11126e5eb3545a8d67c66e546f4ce413c0611573cfbRoshan Pius mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); 1124e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController, never()).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), 113a5a83f137409921fe95dc01214a0307838b8508cxshu anyString()); 114e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 115e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne 11626e5eb3545a8d67c66e546f4ce413c0611573cfbRoshan Pius mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); 1174e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController, never()).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), 118a5a83f137409921fe95dc01214a0307838b8508cxshu anyString()); 119e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 120e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne 121e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne // Verify L.R.Watchdog can still restart things (It has its own complex limiter) 122e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG); 1234e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), 1244e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein anyInt()); 1254e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein reset(mWifiController); 1264e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein 1274e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein // Verify Sta Interface Down will still disable wifi 1284e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein mSelfRecovery.trigger(SelfRecovery.REASON_STA_IFACE_DOWN); 1294e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_DISABLE_WIFI)); 130e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 131e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne 132e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne // now TRAVEL FORWARDS IN TIME and ensure that more restarts can occur 133e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne when(mClock.getElapsedSinceBootMillis()) 134e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne .thenReturn(SelfRecovery.MAX_RESTARTS_TIME_WINDOW_MILLIS + 1); 135e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG); 1364e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), anyInt()); 137e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 138e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne 139e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne when(mClock.getElapsedSinceBootMillis()) 140e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne .thenReturn(SelfRecovery.MAX_RESTARTS_TIME_WINDOW_MILLIS + 1); 14126e5eb3545a8d67c66e546f4ce413c0611573cfbRoshan Pius mSelfRecovery.trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); 1424e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), anyInt()); 143e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 144e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne } 145e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne 146e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne /** 147e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne * Verifies that invocations of {@link SelfRecovery#trigger(int)} for 148e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne * REASON_LAST_RESORT_WATCHDOG are NOT limited to 149e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne * {@link SelfRecovery#MAX_RESTARTS_IN_TIME_WINDOW} in a 150e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne * {@link SelfRecovery#MAX_RESTARTS_TIME_WINDOW_MILLIS} millisecond time window. 151e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne */ 152e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne @Test 153e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne public void testTimeWindowLimiting_lastResortWatchdog_noEffect() { 154e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne for (int i = 0; i < SelfRecovery.MAX_RESTARTS_IN_TIME_WINDOW * 2; i++) { 155e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne // Verify L.R.Watchdog can still restart things (It has it's own complex limiter) 156e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG); 1574e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), 1584e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein anyInt()); 1594e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein reset(mWifiController); 1604e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein } 1614e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein } 1624e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein 1634e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein /** 1644e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein * Verifies that invocations of {@link SelfRecovery#trigger(int)} for 1654e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein * REASON_STA_IFACE_DOWN are NOT limited to 1664e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein * {@link SelfRecovery#MAX_RESTARTS_IN_TIME_WINDOW} in a 1674e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein * {@link SelfRecovery#MAX_RESTARTS_TIME_WINDOW_MILLIS} millisecond time window. 1684e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein */ 1694e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein @Test 1704e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein public void testTimeWindowLimiting_staIfaceDown_noEffect() { 1714e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein for (int i = 0; i < SelfRecovery.MAX_RESTARTS_IN_TIME_WINDOW * 2; i++) { 1724e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein mSelfRecovery.trigger(SelfRecovery.REASON_STA_IFACE_DOWN); 1734e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController).sendMessage(eq(WifiController.CMD_RECOVERY_DISABLE_WIFI)); 1744e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein verify(mWifiController, never()) 1754e1c7af3e5b47bc8f53e92aa25047c5ca03238ceRebecca Silberstein .sendMessage(eq(WifiController.CMD_RECOVERY_RESTART_WIFI), anyInt()); 176e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne reset(mWifiController); 177e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne } 178e8cf3b95869b71ff4719b037f79d74b74d2a2fc3Glen Kuhne } 17996a9dbeb3a622e44c13ff7be8decf36d06ff7dbbRoshan Pius} 180