1package com.android.anqp.eap;
2
3import com.android.anqp.Constants;
4import com.android.hotspot2.AuthMatch;
5
6import java.net.ProtocolException;
7import java.nio.ByteBuffer;
8import java.nio.ByteOrder;
9import java.util.Collections;
10import java.util.EnumMap;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.Map;
14import java.util.Set;
15
16/**
17 * An EAP Method, part of the NAI Realm ANQP element, specified in
18 * IEEE802.11-2012 section 8.4.4.10, figure 8-420
19 */
20public class EAPMethod {
21    private final EAP.EAPMethodID mEAPMethodID;
22    private final Map<EAP.AuthInfoID, Set<AuthParam>> mAuthParams;
23
24    public EAPMethod(ByteBuffer payload) throws ProtocolException {
25        if (payload.remaining() < 3) {
26            throw new ProtocolException("Runt EAP Method: " + payload.remaining());
27        }
28
29        int length = payload.get() & Constants.BYTE_MASK;
30        int methodID = payload.get() & Constants.BYTE_MASK;
31        int count = payload.get() & Constants.BYTE_MASK;
32
33        mEAPMethodID = EAP.mapEAPMethod(methodID);
34        mAuthParams = new EnumMap<>(EAP.AuthInfoID.class);
35
36        int realCount = 0;
37
38        ByteBuffer paramPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
39        paramPayload.limit(paramPayload.position() + length - 2);
40        payload.position(payload.position() + length - 2);
41        while (paramPayload.hasRemaining()) {
42            int id = paramPayload.get() & Constants.BYTE_MASK;
43
44            EAP.AuthInfoID authInfoID = EAP.mapAuthMethod(id);
45            if (authInfoID == null) {
46                throw new ProtocolException("Unknown auth parameter ID: " + id);
47            }
48
49            int len = paramPayload.get() & Constants.BYTE_MASK;
50            if (len == 0 || len > paramPayload.remaining()) {
51                throw new ProtocolException("Bad auth method length: " + len);
52            }
53
54            switch (authInfoID) {
55                case ExpandedEAPMethod:
56                    addAuthParam(new ExpandedEAPMethod(authInfoID, len, paramPayload));
57                    break;
58                case NonEAPInnerAuthType:
59                    addAuthParam(new NonEAPInnerAuth(len, paramPayload));
60                    break;
61                case InnerAuthEAPMethodType:
62                    addAuthParam(new InnerAuthEAP(len, paramPayload));
63                    break;
64                case ExpandedInnerEAPMethod:
65                    addAuthParam(new ExpandedEAPMethod(authInfoID, len, paramPayload));
66                    break;
67                case CredentialType:
68                    addAuthParam(new Credential(authInfoID, len, paramPayload));
69                    break;
70                case TunneledEAPMethodCredType:
71                    addAuthParam(new Credential(authInfoID, len, paramPayload));
72                    break;
73                case VendorSpecific:
74                    addAuthParam(new VendorSpecificAuth(len, paramPayload));
75                    break;
76            }
77
78            realCount++;
79        }
80        if (realCount != count)
81            throw new ProtocolException("Invalid parameter count: " + realCount +
82                    ", expected " + count);
83    }
84
85    public EAPMethod(EAP.EAPMethodID eapMethodID, AuthParam authParam) {
86        mEAPMethodID = eapMethodID;
87        mAuthParams = new HashMap<>(1);
88        if (authParam != null) {
89            Set<AuthParam> authParams = new HashSet<>();
90            authParams.add(authParam);
91            mAuthParams.put(authParam.getAuthInfoID(), authParams);
92        }
93    }
94
95    private void addAuthParam(AuthParam param) {
96        Set<AuthParam> authParams = mAuthParams.get(param.getAuthInfoID());
97        if (authParams == null) {
98            authParams = new HashSet<>();
99            mAuthParams.put(param.getAuthInfoID(), authParams);
100        }
101        authParams.add(param);
102    }
103
104    public Map<EAP.AuthInfoID, Set<AuthParam>> getAuthParams() {
105        return Collections.unmodifiableMap(mAuthParams);
106    }
107
108    public EAP.EAPMethodID getEAPMethodID() {
109        return mEAPMethodID;
110    }
111
112    public int match(com.android.hotspot2.pps.Credential credential) {
113
114        EAPMethod credMethod = credential.getEAPMethod();
115        if (mEAPMethodID != credMethod.getEAPMethodID()) {
116            return AuthMatch.None;
117        }
118
119        switch (mEAPMethodID) {
120            case EAP_TTLS:
121                if (mAuthParams.isEmpty()) {
122                    return AuthMatch.Method;
123                }
124                int paramCount = 0;
125                for (Map.Entry<EAP.AuthInfoID, Set<AuthParam>> entry :
126                        credMethod.getAuthParams().entrySet()) {
127                    Set<AuthParam> params = mAuthParams.get(entry.getKey());
128                    if (params == null) {
129                        continue;
130                    }
131
132                    if (!Collections.disjoint(params, entry.getValue())) {
133                        return AuthMatch.MethodParam;
134                    }
135                    paramCount += params.size();
136                }
137                return paramCount > 0 ? AuthMatch.None : AuthMatch.Method;
138            case EAP_TLS:
139                return AuthMatch.MethodParam;
140            case EAP_SIM:
141            case EAP_AKA:
142            case EAP_AKAPrim:
143                return AuthMatch.Method;
144            default:
145                return AuthMatch.Method;
146        }
147    }
148
149    public AuthParam getAuthParam() {
150        if (mAuthParams.isEmpty()) {
151            return null;
152        }
153        Set<AuthParam> params = mAuthParams.values().iterator().next();
154        if (params.isEmpty()) {
155            return null;
156        }
157        return params.iterator().next();
158    }
159
160    @Override
161    public boolean equals(Object thatObject) {
162        if (this == thatObject) {
163            return true;
164        }
165        else if (thatObject == null || getClass() != thatObject.getClass()) {
166            return false;
167        }
168
169        EAPMethod that = (EAPMethod) thatObject;
170        return mEAPMethodID == that.mEAPMethodID && mAuthParams.equals(that.mAuthParams);
171    }
172
173    @Override
174    public int hashCode() {
175        int result = mEAPMethodID.hashCode();
176        result = 31 * result + mAuthParams.hashCode();
177        return result;
178    }
179
180    @Override
181    public String toString() {
182        StringBuilder sb = new StringBuilder();
183        sb.append("EAP Method ").append(mEAPMethodID).append('\n');
184        for (Set<AuthParam> paramSet : mAuthParams.values()) {
185            for (AuthParam param : paramSet) {
186                sb.append("      ").append(param.toString());
187            }
188        }
189        return sb.toString();
190    }
191}
192