summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
blob: c2ba8af3585745a78b2a16d4089d2a2d5aa0696b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/**
 * Copyright (c) 2013 LEAP Encryption Access Project and contributers
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package se.leap.bitmaskclient.eip;

import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;

import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.LogItem;
import de.blinkt.openvpn.core.VpnStatus;

/**
 * EipStatus is a Singleton that represents a reduced set of a vpn's ConnectionStatus.
 * EipStatus changes it's state (EipLevel) when ConnectionStatus gets updated by OpenVpnService or
 * by VoidVpnService.
 */
public class EipStatus implements VpnStatus.StateListener {
    public static String TAG = EipStatus.class.getSimpleName();
    private static EipStatus currentStatus;

    public enum EipLevel {
        CONNECTING,
        DISCONNECTING,
        CONNECTED,
        DISCONNECTED,
        BLOCKING,
        UNKNOWN
    }

    /**
     * vpnLevel holds the connection status of the openvpn vpn and the traffic blocking
     * void vpn. LEVEL_BLOCKING is set when the latter vpn is up. All other states are set by
     * openvpn.
     */
    private ConnectionStatus vpnLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
    private static EipLevel currentEipLevel = EipLevel.DISCONNECTED;

    private int lastErrorLine = 0;
    private String state, logMessage;
    private int localizedResId;
    private boolean isUpdatingVPNCertificate;

    private final PropertyChangeSupport propertyChange;
    public static final String PROPERTY_CHANGE = "EipStatus";

    public static EipStatus getInstance() {
        if (currentStatus == null) {
            currentStatus = new EipStatus();
            VpnStatus.addStateListener(currentStatus);
        }
        return currentStatus;
    }

    private EipStatus() {
        propertyChange = new PropertyChangeSupport(this);
    }

    @Override
    public void updateState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) {
        ConnectionStatus tmp = getInstance().getLevel();
        getInstance().setState(state);
        getInstance().setLogMessage(logmessage);
        getInstance().setLocalizedResId(localizedResId);
        getInstance().setLevel(level);
        getInstance().setEipLevel(level);
        if (tmp != getInstance().getLevel() || "RECONNECTING".equals(state) || "UI_CONNECTING".equals(state)) {
            refresh();
        }
    }

    @Override
    public void setConnectedVPN(String uuid) {
    }

    public boolean isReconnecting() {
        Log.d(TAG, "eip currentVPNStatus : " + getInstance().getState() );
        return "RECONNECTING".equals(getInstance().getState());
    }

    public boolean isVPNRunningWithoutNetwork() {
        return getInstance().getLevel() == LEVEL_NONETWORK &&
                !"NO_PROCESS".equals(getInstance().getState());
    }

    private void setEipLevel(ConnectionStatus level) {
        switch (level) {
            case LEVEL_CONNECTED:
                currentEipLevel = EipLevel.CONNECTED;
                break;
            case LEVEL_VPNPAUSED:
                if (VpnStatus.getLastConnectedVpnProfile() != null && VpnStatus.getLastConnectedVpnProfile().mPersistTun) {
                    //if persistTun is enabled, treat EipLevel as connecting as it *shouldn't* allow passing traffic in the clear...
                    currentEipLevel = EipLevel.CONNECTING;
                } else {
                    //... if persistTun is not enabled, background network traffic will pass in the clear
                    currentEipLevel = EipLevel.DISCONNECTED;
                }
                break;
            case LEVEL_CONNECTING_SERVER_REPLIED:
            case LEVEL_CONNECTING_NO_SERVER_REPLY_YET:
            case LEVEL_WAITING_FOR_USER_INPUT:
            case LEVEL_START:
                currentEipLevel = EipLevel.CONNECTING;
                break;
            case LEVEL_AUTH_FAILED:
            case LEVEL_NOTCONNECTED:
                currentEipLevel = EipLevel.DISCONNECTED;
                break;
            case LEVEL_STOPPING:
                currentEipLevel = EipLevel.DISCONNECTING;
                break;
            case LEVEL_NONETWORK:
            case LEVEL_BLOCKING:
                setEipLevelWithDelay(level);
                break;
            case UNKNOWN_LEVEL:
                currentEipLevel = EipLevel.UNKNOWN; //??
                break;
        }
    }

    public EipLevel getEipLevel() {
        return currentEipLevel;
    }

    /**
     * This is a debouncing method ignoring states that are valid for less than a second.
     * This way flickering UI changes can be avoided.
     *
     * @param futureLevel
     */
    private void setEipLevelWithDelay(ConnectionStatus futureLevel) {
        new DelayTask(getInstance().getLevel(), futureLevel).execute();
    }

    private static class DelayTask extends AsyncTask<Void, Void, Void> {

        private final ConnectionStatus currentLevel;
        private final ConnectionStatus futureLevel;

        DelayTask(ConnectionStatus currentLevel, ConnectionStatus futureLevel) {
            this.currentLevel = currentLevel;
            this.futureLevel = futureLevel;
        }
        protected Void doInBackground(Void... levels) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.interrupted();
            }
            return null;
        }

        protected void onPostExecute(Void result) {
            if (currentLevel == getInstance().getLevel()) {
                switch (futureLevel) {
                    case LEVEL_NONETWORK:
                        currentEipLevel = EipLevel.DISCONNECTED;
                        break;
                    case LEVEL_BLOCKING:
                        currentEipLevel = EipLevel.BLOCKING;
                        break;
                    default:
                        break;
                }
                refresh();
            }
        }
    }

    public void setUpdatingVpnCert(boolean isUpdating) {
        isUpdatingVPNCertificate = isUpdating;
        refresh();
    }

    public boolean isUpdatingVpnCert() {
        return isUpdatingVPNCertificate;
    }

    public boolean isConnecting() {
        return currentEipLevel == EipLevel.CONNECTING;
    }

    public boolean isConnected() {
        return currentEipLevel == EipLevel.CONNECTED;
    }

    /**
     * @return true if currentEipLevel is for at least a second {@link EipLevel#BLOCKING}.
     * See {@link #setEipLevelWithDelay(ConnectionStatus)}.
     */
    public boolean isBlocking() {
        return currentEipLevel == EipLevel.BLOCKING;
    }

    /**
     *
     * @return true immediately after traffic blocking VoidVpn was established.
     */
    public boolean isBlockingVpnEstablished() {
        return vpnLevel == ConnectionStatus.LEVEL_BLOCKING;
    }

    public boolean isDisconnected() {
        return currentEipLevel == EipLevel.DISCONNECTED;
    }

    public boolean isDisconnecting() {
        return currentEipLevel == EipLevel.DISCONNECTING;
    }

    /**
     * ics-openvpn's paused state is not implemented yet
     * @return true if vpn is paused false if not
     */
    @Deprecated
    public boolean isPaused() {
        return vpnLevel == ConnectionStatus.LEVEL_VPNPAUSED;
    }

    public String getState() {
        return state;
    }

    public String getLogMessage() {
        return logMessage;
    }

    int getLocalizedResId() {
        return localizedResId;
    }

    public ConnectionStatus getLevel() {
        return vpnLevel;
    }

    private void setState(String state) {
        this.state = state;
    }

    private void setLogMessage(String log_message) {
        this.logMessage = log_message;
    }

    private void setLocalizedResId(int localized_res_id) {
        this.localizedResId = localized_res_id;
    }

    private void setLevel(ConnectionStatus level) {
        this.vpnLevel = level;
    }

    public boolean errorInLast(int lines, Context context) {
        return !lastError(lines, context).isEmpty();
    }

    private String lastError(int lines, Context context) {
        String error = "";

        String[] error_keywords = {"error", "ERROR", "fatal", "FATAL"};

        LogItem[] log = VpnStatus.getlogbuffer();
        if(log.length < lastErrorLine)
            lastErrorLine = 0;
        String message;
        for (int i = 1; i <= lines && log.length > i; i++) {
            int line = log.length - i;
            LogItem logItem = log[line];
            message = logItem.getString(context);
            for (String errorKeyword: error_keywords) {
                if (message.contains(errorKeyword) && line > lastErrorLine) {
                    error = message;
                    lastErrorLine = line;
                }
            }
        }

        return error;
    }

    @Override
    public String toString() {
        return "State: " + state + " Level: " + vpnLevel.toString();
    }

    public static void refresh() {
        currentStatus.propertyChange.firePropertyChange(PROPERTY_CHANGE, null, currentStatus);
    }

    public void addObserver(PropertyChangeListener propertyChangeListener) {
        propertyChange.addPropertyChangeListener(propertyChangeListener);
    }

    public void deleteObserver(PropertyChangeListener propertyChangeListener) {
        propertyChange.removePropertyChangeListener(propertyChangeListener);
    }
}