1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.net.eap.message.simaka;
18 
19 import static com.android.internal.net.eap.EapAuthenticator.LOG;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAtPaddingException;
23 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException;
24 import com.android.internal.net.eap.exceptions.simaka.EapSimInvalidAtRandException;
25 
26 import java.nio.ByteBuffer;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 
33 /**
34  * EapSimAkaAttribute represents a single EAP SIM/AKA Attribute.
35  *
36  * @see <a href="https://tools.ietf.org/html/rfc4186">RFC 4186, Extensible Authentication
37  * Protocol for Subscriber Identity Modules (EAP-SIM)</a>
38  * @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication
39  * Protocol for Authentication and Key Agreement (EAP-AKA)</a>
40  * @see <a href="https://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml">EAP SIM/AKA
41  * Attributes</a>
42  */
43 public abstract class EapSimAkaAttribute {
44     static final int LENGTH_SCALING = 4;
45 
46     private static final int MIN_ATTR_LENGTH = 4;
47 
48     public static final int SKIPPABLE_ATTRIBUTE_RANGE_START = 128;
49 
50     // EAP non-Skippable Attribute values defined by IANA
51     // https://www.iana.org/assignments/eapsimaka-numbers/eapsimaka-numbers.xhtml
52     public static final int EAP_AT_RAND = 1;
53     public static final int EAP_AT_AUTN = 2;
54     public static final int EAP_AT_RES = 3;
55     public static final int EAP_AT_AUTS = 4;
56     public static final int EAP_AT_PADDING = 6;
57     public static final int EAP_AT_NONCE_MT = 7;
58     public static final int EAP_AT_PERMANENT_ID_REQ = 10;
59     public static final int EAP_AT_MAC = 11;
60     public static final int EAP_AT_NOTIFICATION = 12;
61     public static final int EAP_AT_ANY_ID_REQ = 13;
62     public static final int EAP_AT_IDENTITY = 14;
63     public static final int EAP_AT_VERSION_LIST = 15;
64     public static final int EAP_AT_SELECTED_VERSION = 16;
65     public static final int EAP_AT_FULLAUTH_ID_REQ = 17;
66     public static final int EAP_AT_COUNTER = 19;
67     public static final int EAP_AT_COUNTER_TOO_SMALL = 20;
68     public static final int EAP_AT_NONCE_S = 21;
69     public static final int EAP_AT_CLIENT_ERROR_CODE = 22;
70     public static final int EAP_AT_KDF_INPUT = 23;
71     public static final int EAP_AT_KDF = 24;
72 
73     // EAP Skippable Attribute values defined by IANA
74     // https://www.iana.org/assignments/eapsimaka-numbers/eapsimaka-numbers.xhtml
75     public static final int EAP_AT_IV = 129;
76     public static final int EAP_AT_ENCR_DATA = 130;
77     public static final int EAP_AT_NEXT_PSEUDONYM = 132;
78     public static final int EAP_AT_NEXT_REAUTH_ID = 133;
79     public static final int EAP_AT_CHECKCODE = 134;
80     public static final int EAP_AT_RESULT_IND = 135;
81     public static final int EAP_AT_BIDDING = 136;
82 
83     public static final Map<Integer, String> EAP_ATTRIBUTE_STRING = new HashMap<>();
84     static {
EAP_ATTRIBUTE_STRING.put(EAP_AT_RAND, "AT_RAND")85         EAP_ATTRIBUTE_STRING.put(EAP_AT_RAND, "AT_RAND");
EAP_ATTRIBUTE_STRING.put(EAP_AT_AUTN, "AT_AUTN")86         EAP_ATTRIBUTE_STRING.put(EAP_AT_AUTN, "AT_AUTN");
EAP_ATTRIBUTE_STRING.put(EAP_AT_RES, "AT_RES")87         EAP_ATTRIBUTE_STRING.put(EAP_AT_RES, "AT_RES");
EAP_ATTRIBUTE_STRING.put(EAP_AT_AUTS, "AT_AUTS")88         EAP_ATTRIBUTE_STRING.put(EAP_AT_AUTS, "AT_AUTS");
EAP_ATTRIBUTE_STRING.put(EAP_AT_PADDING, "AT_PADDING")89         EAP_ATTRIBUTE_STRING.put(EAP_AT_PADDING, "AT_PADDING");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NONCE_MT, "AT_NONCE_MT")90         EAP_ATTRIBUTE_STRING.put(EAP_AT_NONCE_MT, "AT_NONCE_MT");
EAP_ATTRIBUTE_STRING.put(EAP_AT_PERMANENT_ID_REQ, "AT_PERMANENT_ID_REQ")91         EAP_ATTRIBUTE_STRING.put(EAP_AT_PERMANENT_ID_REQ, "AT_PERMANENT_ID_REQ");
EAP_ATTRIBUTE_STRING.put(EAP_AT_MAC, "AT_MAC")92         EAP_ATTRIBUTE_STRING.put(EAP_AT_MAC, "AT_MAC");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NOTIFICATION, "AT_NOTIFICATION")93         EAP_ATTRIBUTE_STRING.put(EAP_AT_NOTIFICATION, "AT_NOTIFICATION");
EAP_ATTRIBUTE_STRING.put(EAP_AT_ANY_ID_REQ, "AT_ANY_ID_REQ")94         EAP_ATTRIBUTE_STRING.put(EAP_AT_ANY_ID_REQ, "AT_ANY_ID_REQ");
EAP_ATTRIBUTE_STRING.put(EAP_AT_IDENTITY, "AT_IDENTITY")95         EAP_ATTRIBUTE_STRING.put(EAP_AT_IDENTITY, "AT_IDENTITY");
EAP_ATTRIBUTE_STRING.put(EAP_AT_VERSION_LIST, "AT_VERSION_LIST")96         EAP_ATTRIBUTE_STRING.put(EAP_AT_VERSION_LIST, "AT_VERSION_LIST");
EAP_ATTRIBUTE_STRING.put(EAP_AT_SELECTED_VERSION, "AT_SELECTED_VERSION")97         EAP_ATTRIBUTE_STRING.put(EAP_AT_SELECTED_VERSION, "AT_SELECTED_VERSION");
EAP_ATTRIBUTE_STRING.put(EAP_AT_FULLAUTH_ID_REQ, "AT_FULLAUTH_ID_REQ")98         EAP_ATTRIBUTE_STRING.put(EAP_AT_FULLAUTH_ID_REQ, "AT_FULLAUTH_ID_REQ");
EAP_ATTRIBUTE_STRING.put(EAP_AT_COUNTER, "AT_COUNTER")99         EAP_ATTRIBUTE_STRING.put(EAP_AT_COUNTER, "AT_COUNTER");
EAP_ATTRIBUTE_STRING.put(EAP_AT_COUNTER_TOO_SMALL, "AT_COUNTER_TOO_SMALL")100         EAP_ATTRIBUTE_STRING.put(EAP_AT_COUNTER_TOO_SMALL, "AT_COUNTER_TOO_SMALL");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NONCE_S, "AT_NONCE_S")101         EAP_ATTRIBUTE_STRING.put(EAP_AT_NONCE_S, "AT_NONCE_S");
EAP_ATTRIBUTE_STRING.put(EAP_AT_CLIENT_ERROR_CODE, "AT_CLIENT_ERROR_CODE")102         EAP_ATTRIBUTE_STRING.put(EAP_AT_CLIENT_ERROR_CODE, "AT_CLIENT_ERROR_CODE");
EAP_ATTRIBUTE_STRING.put(EAP_AT_KDF_INPUT, "AT_KDF_INPUT")103         EAP_ATTRIBUTE_STRING.put(EAP_AT_KDF_INPUT, "AT_KDF_INPUT");
EAP_ATTRIBUTE_STRING.put(EAP_AT_KDF, "AT_KDF")104         EAP_ATTRIBUTE_STRING.put(EAP_AT_KDF, "AT_KDF");
105 
EAP_ATTRIBUTE_STRING.put(EAP_AT_IV, "AT_IV")106         EAP_ATTRIBUTE_STRING.put(EAP_AT_IV, "AT_IV");
EAP_ATTRIBUTE_STRING.put(EAP_AT_ENCR_DATA, "AT_ENCR_DATA")107         EAP_ATTRIBUTE_STRING.put(EAP_AT_ENCR_DATA, "AT_ENCR_DATA");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NEXT_PSEUDONYM, "AT_NEXT_PSEUDONYM")108         EAP_ATTRIBUTE_STRING.put(EAP_AT_NEXT_PSEUDONYM, "AT_NEXT_PSEUDONYM");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NEXT_REAUTH_ID, "AT_NEXT_REAUTH_ID")109         EAP_ATTRIBUTE_STRING.put(EAP_AT_NEXT_REAUTH_ID, "AT_NEXT_REAUTH_ID");
EAP_ATTRIBUTE_STRING.put(EAP_AT_CHECKCODE, "AT_CHECKCODE")110         EAP_ATTRIBUTE_STRING.put(EAP_AT_CHECKCODE, "AT_CHECKCODE");
EAP_ATTRIBUTE_STRING.put(EAP_AT_RESULT_IND, "AT_RESULT_IND")111         EAP_ATTRIBUTE_STRING.put(EAP_AT_RESULT_IND, "AT_RESULT_IND");
EAP_ATTRIBUTE_STRING.put(EAP_AT_BIDDING, "AT_BIDDING")112         EAP_ATTRIBUTE_STRING.put(EAP_AT_BIDDING, "AT_BIDDING");
113     }
114 
115     public final int attributeType;
116     public final int lengthInBytes;
117 
EapSimAkaAttribute(int attributeType, int lengthInBytes)118     protected EapSimAkaAttribute(int attributeType, int lengthInBytes)
119             throws EapSimAkaInvalidAttributeException {
120         this.attributeType = attributeType;
121         this.lengthInBytes = lengthInBytes;
122 
123         if (lengthInBytes % LENGTH_SCALING != 0) {
124             throw new EapSimAkaInvalidAttributeException("Attribute length must be multiple of 4");
125         }
126     }
127 
128     /**
129      * Encodes this EapSimAkaAttribute into the given ByteBuffer
130      *
131      * @param byteBuffer the ByteBuffer that this instance will be written to
132      */
encode(ByteBuffer byteBuffer)133     public abstract void encode(ByteBuffer byteBuffer);
134 
encodeAttributeHeader(ByteBuffer byteBuffer)135     protected void encodeAttributeHeader(ByteBuffer byteBuffer) {
136         byteBuffer.put((byte) attributeType);
137         byteBuffer.put((byte) (lengthInBytes / LENGTH_SCALING));
138     }
139 
consumePadding(int bytesUsed, ByteBuffer byteBuffer)140     void consumePadding(int bytesUsed, ByteBuffer byteBuffer) {
141         int paddingRemaining = lengthInBytes - bytesUsed;
142         byteBuffer.get(new byte[paddingRemaining]);
143     }
144 
addPadding(int bytesUsed, ByteBuffer byteBuffer)145     void addPadding(int bytesUsed, ByteBuffer byteBuffer) {
146         int paddingNeeded = lengthInBytes - bytesUsed;
147         byteBuffer.put(new byte[paddingNeeded]);
148     }
149 
150     /**
151      * EapSimAkaUnsupportedAttribute represents any unsupported, skippable EAP-SIM attribute.
152      */
153     public static class EapSimAkaUnsupportedAttribute extends EapSimAkaAttribute {
154         // Attribute Type (1B) + Attribute Length (1B) = 2B Header
155         private static final int HEADER_BYTES = 2;
156 
157         public final byte[] data;
158 
EapSimAkaUnsupportedAttribute( int attributeType, int lengthInBytes, ByteBuffer byteBuffer)159         public EapSimAkaUnsupportedAttribute(
160                 int attributeType,
161                 int lengthInBytes,
162                 ByteBuffer byteBuffer) throws EapSimAkaInvalidAttributeException {
163             super(attributeType, lengthInBytes);
164 
165             // Attribute not supported, but remaining attribute still needs to be saved
166             int remainingBytes = lengthInBytes - HEADER_BYTES;
167             data = new byte[remainingBytes];
168             byteBuffer.get(data);
169         }
170 
171         @VisibleForTesting
EapSimAkaUnsupportedAttribute(int attributeType, int lengthInBytes, byte[] data)172         public EapSimAkaUnsupportedAttribute(int attributeType, int lengthInBytes, byte[] data)
173                 throws EapSimAkaInvalidAttributeException {
174             super(attributeType, lengthInBytes);
175             this.data = data;
176         }
177 
178         @Override
encode(ByteBuffer byteBuffer)179         public void encode(ByteBuffer byteBuffer) {
180             encodeAttributeHeader(byteBuffer);
181             byteBuffer.put(data);
182         }
183     }
184 
185     /**
186      * AtVersionList represents the AT_VERSION_LIST attribute defined in RFC 4186#10.2
187      */
188     public static class AtVersionList extends EapSimAkaAttribute {
189         private static final int BYTES_PER_VERSION = 2;
190 
191         public final List<Integer> versions = new ArrayList<>();
192 
AtVersionList(int lengthInBytes, ByteBuffer byteBuffer)193         public AtVersionList(int lengthInBytes, ByteBuffer byteBuffer)
194                 throws EapSimAkaInvalidAttributeException {
195             super(EAP_AT_VERSION_LIST, lengthInBytes);
196 
197             // number of bytes used to represent list (RFC 4186 Section 10.2)
198             int bytesInList = Short.toUnsignedInt(byteBuffer.getShort());
199             if (bytesInList % BYTES_PER_VERSION != 0) {
200                 throw new EapSimAkaInvalidAttributeException(
201                         "Actual Version List Length must be multiple of 2");
202             }
203 
204             int numVersions =  bytesInList / BYTES_PER_VERSION;
205             for (int i = 0; i < numVersions; i++) {
206                 versions.add(Short.toUnsignedInt(byteBuffer.getShort()));
207             }
208 
209             int bytesUsed = MIN_ATTR_LENGTH + (BYTES_PER_VERSION * versions.size());
210             consumePadding(bytesUsed, byteBuffer);
211         }
212 
213         @VisibleForTesting
AtVersionList(int lengthInBytes, int... versions)214         public AtVersionList(int lengthInBytes, int... versions)
215                 throws EapSimAkaInvalidAttributeException {
216             super(EAP_AT_VERSION_LIST, lengthInBytes);
217             for (int version : versions) {
218                 this.versions.add(version);
219             }
220         }
221 
222         @Override
encode(ByteBuffer byteBuffer)223         public void encode(ByteBuffer byteBuffer) {
224             encodeAttributeHeader(byteBuffer);
225 
226             byteBuffer.putShort((short) (versions.size() * BYTES_PER_VERSION));
227             for (int i : versions) {
228                 byteBuffer.putShort((short) i);
229             }
230 
231             int bytesUsed = MIN_ATTR_LENGTH + (BYTES_PER_VERSION * versions.size());
232             addPadding(bytesUsed, byteBuffer);
233         }
234     }
235 
236     /**
237      * AtSelectedVersion represents the AT_SELECTED_VERSION attribute defined in RFC 4186#10.3
238      */
239     public static class AtSelectedVersion extends EapSimAkaAttribute {
240         private static final String TAG = AtSelectedVersion.class.getSimpleName();
241         private static final int LENGTH = LENGTH_SCALING;
242 
243         public static final int SUPPORTED_VERSION = 1;
244 
245         public final int selectedVersion;
246 
AtSelectedVersion(int lengthInBytes, int selectedVersion)247         public AtSelectedVersion(int lengthInBytes, int selectedVersion)
248                 throws EapSimAkaInvalidAttributeException {
249             super(EAP_AT_SELECTED_VERSION, LENGTH);
250             this.selectedVersion = selectedVersion;
251 
252             if (lengthInBytes != LENGTH) {
253                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
254             }
255         }
256 
257         @VisibleForTesting
AtSelectedVersion(int selectedVersion)258         public AtSelectedVersion(int selectedVersion) throws EapSimAkaInvalidAttributeException {
259             super(EAP_AT_SELECTED_VERSION, LENGTH);
260             this.selectedVersion = selectedVersion;
261         }
262 
263         @Override
encode(ByteBuffer byteBuffer)264         public void encode(ByteBuffer byteBuffer) {
265             encodeAttributeHeader(byteBuffer);
266             byteBuffer.putShort((short) selectedVersion);
267         }
268 
269         /**
270          * Constructs and returns an AtSelectedVersion for the only supported version of EAP-SIM
271          *
272          * @return an AtSelectedVersion for the supported version (1) of EAP-SIM
273          */
getSelectedVersion()274         public static AtSelectedVersion getSelectedVersion() {
275             try {
276                 return new AtSelectedVersion(LENGTH, SUPPORTED_VERSION);
277             } catch (EapSimAkaInvalidAttributeException ex) {
278                 // this should never happen
279                 LOG.wtf(TAG,
280                         "Error thrown while creating AtSelectedVersion with correct length", ex);
281                 throw new AssertionError("Impossible exception encountered", ex);
282             }
283         }
284     }
285 
286     /**
287      * AtNonceMt represents the AT_NONCE_MT attribute defined in RFC 4186#10.4
288      */
289     public static class AtNonceMt extends EapSimAkaAttribute {
290         private static final int LENGTH = 5 * LENGTH_SCALING;
291         private static final int RESERVED_BYTES = 2;
292 
293         public static final int NONCE_MT_LENGTH = 16;
294 
295         public final byte[] nonceMt = new byte[NONCE_MT_LENGTH];
296 
AtNonceMt(int lengthInBytes, ByteBuffer byteBuffer)297         public AtNonceMt(int lengthInBytes, ByteBuffer byteBuffer)
298                 throws EapSimAkaInvalidAttributeException {
299             super(EAP_AT_NONCE_MT, LENGTH);
300             if (lengthInBytes != LENGTH) {
301                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
302             }
303 
304             // next two bytes are reserved (RFC 4186 Section 10.4)
305             byteBuffer.get(new byte[RESERVED_BYTES]);
306             byteBuffer.get(nonceMt);
307         }
308 
309         @VisibleForTesting
AtNonceMt(byte[] nonceMt)310         public AtNonceMt(byte[] nonceMt) throws EapSimAkaInvalidAttributeException {
311             super(EAP_AT_NONCE_MT, LENGTH);
312 
313             if (nonceMt.length != NONCE_MT_LENGTH) {
314                 throw new EapSimAkaInvalidAttributeException("NonceMt length must be 16B");
315             }
316             System.arraycopy(nonceMt, 0, this.nonceMt, 0, NONCE_MT_LENGTH);
317         }
318 
319         @Override
encode(ByteBuffer byteBuffer)320         public void encode(ByteBuffer byteBuffer) {
321             encodeAttributeHeader(byteBuffer);
322             byteBuffer.put(new byte[RESERVED_BYTES]);
323             byteBuffer.put(nonceMt);
324         }
325     }
326 
327     private abstract static class AtIdReq extends EapSimAkaAttribute {
328         private static final int ATTR_LENGTH = LENGTH_SCALING;
329         private static final int RESERVED_BYTES = 2;
330 
AtIdReq(int lengthInBytes, int attributeType, ByteBuffer byteBuffer)331         protected AtIdReq(int lengthInBytes, int attributeType, ByteBuffer byteBuffer)
332                 throws EapSimAkaInvalidAttributeException {
333             super(attributeType, ATTR_LENGTH);
334 
335             if (lengthInBytes != ATTR_LENGTH) {
336                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
337             }
338 
339             // next two bytes are reserved (RFC 4186 Section 10.5-10.7)
340             byteBuffer.get(new byte[RESERVED_BYTES]);
341         }
342 
343         @VisibleForTesting
AtIdReq(int attributeType)344         protected AtIdReq(int attributeType) throws EapSimAkaInvalidAttributeException {
345             super(attributeType, ATTR_LENGTH);
346         }
347 
348         @Override
encode(ByteBuffer byteBuffer)349         public void encode(ByteBuffer byteBuffer) {
350             encodeAttributeHeader(byteBuffer);
351             byteBuffer.put(new byte[RESERVED_BYTES]);
352         }
353     }
354 
355     /**
356      * AtPermanentIdReq represents the AT_PERMANENT_ID_REQ attribute defined in RFC 4186#10.5 and
357      * RFC 4187#10.2
358      */
359     public static class AtPermanentIdReq extends AtIdReq {
AtPermanentIdReq(int lengthInBytes, ByteBuffer byteBuffer)360         public AtPermanentIdReq(int lengthInBytes, ByteBuffer byteBuffer)
361                 throws EapSimAkaInvalidAttributeException {
362             super(lengthInBytes, EAP_AT_PERMANENT_ID_REQ, byteBuffer);
363         }
364 
365         @VisibleForTesting
AtPermanentIdReq()366         public AtPermanentIdReq() throws EapSimAkaInvalidAttributeException {
367             super(EAP_AT_PERMANENT_ID_REQ);
368         }
369     }
370 
371     /**
372      * AtAnyIdReq represents the AT_ANY_ID_REQ attribute defined in RFC 4186#10.6 and RFC 4187#10.3
373      */
374     public static class AtAnyIdReq extends AtIdReq {
AtAnyIdReq(int lengthInBytes, ByteBuffer byteBuffer)375         public AtAnyIdReq(int lengthInBytes, ByteBuffer byteBuffer)
376                 throws EapSimAkaInvalidAttributeException {
377             super(lengthInBytes, EAP_AT_ANY_ID_REQ, byteBuffer);
378         }
379 
380         @VisibleForTesting
AtAnyIdReq()381         public AtAnyIdReq() throws EapSimAkaInvalidAttributeException {
382             super(EAP_AT_ANY_ID_REQ);
383         }
384     }
385 
386     /**
387      * AtFullauthIdReq represents the AT_FULLAUTH_ID_REQ attribute defined in RFC 4186#10.7 and RFC
388      * 4187#10.4
389      */
390     public static class AtFullauthIdReq extends AtIdReq {
AtFullauthIdReq(int lengthInBytes, ByteBuffer byteBuffer)391         public AtFullauthIdReq(int lengthInBytes, ByteBuffer byteBuffer)
392                 throws EapSimAkaInvalidAttributeException {
393             super(lengthInBytes, EAP_AT_FULLAUTH_ID_REQ, byteBuffer);
394         }
395 
396         @VisibleForTesting
AtFullauthIdReq()397         public AtFullauthIdReq() throws EapSimAkaInvalidAttributeException {
398             super(EAP_AT_FULLAUTH_ID_REQ);
399         }
400     }
401 
402     /**
403      * AtIdentity represents the AT_IDENTITY attribute defined in RFC 4186#10.8 and RFC 4187#10.5
404      */
405     public static class AtIdentity extends EapSimAkaAttribute {
406         public final byte[] identity;
407 
AtIdentity(int lengthInBytes, ByteBuffer byteBuffer)408         public AtIdentity(int lengthInBytes, ByteBuffer byteBuffer)
409                 throws EapSimAkaInvalidAttributeException {
410             super(EAP_AT_IDENTITY, lengthInBytes);
411 
412             int identityLength = Short.toUnsignedInt(byteBuffer.getShort());
413             identity = new byte[identityLength];
414             byteBuffer.get(identity);
415 
416             int bytesUsed = MIN_ATTR_LENGTH + identityLength;
417             consumePadding(bytesUsed, byteBuffer);
418         }
419 
420         @VisibleForTesting
AtIdentity(int lengthInBytes, byte[] identity)421         public AtIdentity(int lengthInBytes, byte[] identity)
422                 throws EapSimAkaInvalidAttributeException {
423             super(EAP_AT_IDENTITY, lengthInBytes);
424             this.identity = identity;
425         }
426 
427         @Override
encode(ByteBuffer byteBuffer)428         public void encode(ByteBuffer byteBuffer) {
429             encodeAttributeHeader(byteBuffer);
430             byteBuffer.putShort((short) identity.length);
431             byteBuffer.put(identity);
432 
433             int bytesUsed = MIN_ATTR_LENGTH + identity.length;
434             addPadding(bytesUsed, byteBuffer);
435         }
436 
437         /**
438          * Creates and returns an AtIdentity instance for the given identity.
439          *
440          * @param identity byte-array representing the identity for the AtIdentity
441          * @return AtIdentity instance for the given identity byte-array
442          */
getAtIdentity(byte[] identity)443         public static AtIdentity getAtIdentity(byte[] identity)
444                 throws EapSimAkaInvalidAttributeException {
445             int lengthInBytes = MIN_ATTR_LENGTH + identity.length;
446             if (lengthInBytes % LENGTH_SCALING != 0) {
447                 lengthInBytes += LENGTH_SCALING - (lengthInBytes % LENGTH_SCALING);
448             }
449 
450             return new AtIdentity(lengthInBytes, identity);
451         }
452     }
453 
454     /**
455      * AtRandSim represents the AT_RAND attribute for EAP-SIM defined in RFC 4186#10.9
456      */
457     public static class AtRandSim extends EapSimAkaAttribute {
458         private static final int RAND_LENGTH = 16;
459         private static final int RESERVED_BYTES = 2;
460         private static final int MIN_RANDS = 2;
461         private static final int MAX_RANDS = 3;
462 
463         public final List<byte[]> rands = new ArrayList<>(MAX_RANDS);
464 
AtRandSim(int lengthInBytes, ByteBuffer byteBuffer)465         public AtRandSim(int lengthInBytes, ByteBuffer byteBuffer)
466                 throws EapSimAkaInvalidAttributeException {
467             super(EAP_AT_RAND, lengthInBytes);
468 
469             // next two bytes are reserved (RFC 4186 Section 10.9)
470             byteBuffer.get(new byte[RESERVED_BYTES]);
471 
472             int numRands = (lengthInBytes - MIN_ATTR_LENGTH) / RAND_LENGTH;
473             if (!isValidNumRands(numRands)) {
474                 throw new EapSimInvalidAtRandException("Unexpected number of rands: " + numRands);
475             }
476 
477             for (int i = 0; i < numRands; i++) {
478                 byte[] rand = new byte[RAND_LENGTH];
479                 byteBuffer.get(rand);
480 
481                 // check for rand being unique (RFC 4186 Section 10.9)
482                 for (int j = 0; j < i; j++) {
483                     byte[] otherRand = rands.get(j);
484                     if (Arrays.equals(rand, otherRand)) {
485                         throw new EapSimAkaInvalidAttributeException("Received identical RANDs");
486                     }
487                 }
488                 rands.add(rand);
489             }
490         }
491 
492         @VisibleForTesting
AtRandSim(int lengthInBytes, byte[]... rands)493         public AtRandSim(int lengthInBytes, byte[]... rands)
494                 throws EapSimAkaInvalidAttributeException {
495             super(EAP_AT_RAND, lengthInBytes);
496 
497             if (!isValidNumRands(rands.length)) {
498                 throw new EapSimInvalidAtRandException("Unexpected number of rands: "
499                         + rands.length);
500             }
501             for (byte[] rand : rands) {
502                 this.rands.add(rand);
503             }
504         }
505 
isValidNumRands(int numRands)506         private boolean isValidNumRands(int numRands) {
507             // numRands is valid iff 2 <= numRands <= 3
508             return MIN_RANDS <= numRands && numRands <= MAX_RANDS;
509         }
510 
511         @Override
encode(ByteBuffer byteBuffer)512         public void encode(ByteBuffer byteBuffer) {
513             encodeAttributeHeader(byteBuffer);
514             byteBuffer.put(new byte[RESERVED_BYTES]);
515 
516             for (byte[] rand : rands) {
517                 byteBuffer.put(rand);
518             }
519         }
520     }
521 
522     /**
523      * AtRandAka represents the AT_RAND attribute for EAP-AKA defined in RFC 4187#10.6
524      */
525     public static class AtRandAka extends EapSimAkaAttribute {
526         private static final int ATTR_LENGTH = 5 * LENGTH_SCALING;
527         private static final int RAND_LENGTH = 16;
528         private static final int RESERVED_BYTES = 2;
529 
530         public final byte[] rand = new byte[RAND_LENGTH];
531 
AtRandAka(int lengthInBytes, ByteBuffer byteBuffer)532         public AtRandAka(int lengthInBytes, ByteBuffer byteBuffer)
533                 throws EapSimAkaInvalidAttributeException {
534             super(EAP_AT_RAND, lengthInBytes);
535 
536             if (lengthInBytes != ATTR_LENGTH) {
537                 throw new EapSimAkaInvalidAttributeException("Length must be 20B");
538             }
539 
540             // next two bytes are reserved (RFC 4187#10.6)
541             byteBuffer.get(new byte[RESERVED_BYTES]);
542 
543             byteBuffer.get(rand);
544         }
545 
546         @VisibleForTesting
AtRandAka(byte[] rand)547         public AtRandAka(byte[] rand)
548                 throws EapSimAkaInvalidAttributeException {
549             super(EAP_AT_RAND, ATTR_LENGTH);
550 
551             if (rand.length != RAND_LENGTH) {
552                 throw new EapSimAkaInvalidAttributeException("Rand must be 16B");
553             }
554 
555             System.arraycopy(rand, 0, this.rand, 0, RAND_LENGTH);
556         }
557 
558         @Override
encode(ByteBuffer byteBuffer)559         public void encode(ByteBuffer byteBuffer) {
560             encodeAttributeHeader(byteBuffer);
561             byteBuffer.put(new byte[RESERVED_BYTES]);
562             byteBuffer.put(rand);
563         }
564     }
565 
566     /**
567      * AtPadding represents the AT_PADDING attribute defined in RFC 4186#10.12 and RFC 4187#10.12
568      */
569     public static class AtPadding extends EapSimAkaAttribute {
570         private static final int ATTR_HEADER = 2;
571 
AtPadding(int lengthInBytes, ByteBuffer byteBuffer)572         public AtPadding(int lengthInBytes, ByteBuffer byteBuffer)
573                 throws EapSimAkaInvalidAttributeException {
574             super(EAP_AT_PADDING, lengthInBytes);
575 
576             int remainingBytes = lengthInBytes - ATTR_HEADER;
577             for (int i = 0; i < remainingBytes; i++) {
578                 // Padding must be checked to all be 0x00 bytes (RFC 4186 Section 10.12)
579                 if (byteBuffer.get() != 0) {
580                     throw new EapSimAkaInvalidAtPaddingException("Padding bytes must all be 0x00");
581                 }
582             }
583         }
584 
585         @VisibleForTesting
AtPadding(int lengthInBytes)586         public AtPadding(int lengthInBytes) throws EapSimAkaInvalidAttributeException {
587             super(EAP_AT_PADDING, lengthInBytes);
588         }
589 
590         @Override
encode(ByteBuffer byteBuffer)591         public void encode(ByteBuffer byteBuffer) {
592             encodeAttributeHeader(byteBuffer);
593 
594             addPadding(ATTR_HEADER, byteBuffer);
595         }
596     }
597 
598     /**
599      * AtMac represents the AT_MAC attribute defined in RFC 4186#10.14 and RFC 4187#10.15
600      */
601     public static class AtMac extends EapSimAkaAttribute {
602         private static final int ATTR_LENGTH = 5 * LENGTH_SCALING;
603         private static final int RESERVED_BYTES = 2;
604 
605         public static final int MAC_LENGTH = 4 * LENGTH_SCALING;
606 
607         public final byte[] mac;
608 
AtMac(int lengthInBytes, ByteBuffer byteBuffer)609         public AtMac(int lengthInBytes, ByteBuffer byteBuffer)
610                 throws EapSimAkaInvalidAttributeException {
611             super(EAP_AT_MAC, lengthInBytes);
612 
613             if (lengthInBytes != ATTR_LENGTH) {
614                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
615             }
616 
617             // next two bytes are reserved (RFC 4186 Section 10.14)
618             byteBuffer.get(new byte[RESERVED_BYTES]);
619 
620             mac = new byte[MAC_LENGTH];
621             byteBuffer.get(mac);
622         }
623 
624         // Used for calculating MACs. Per RFC 4186 Section 10.14, the MAC should be calculated over
625         // the entire packet, with the value field of the MAC attribute set to zero.
AtMac()626         public AtMac() throws EapSimAkaInvalidAttributeException {
627             super(EAP_AT_MAC, ATTR_LENGTH);
628             mac = new byte[MAC_LENGTH];
629         }
630 
AtMac(byte[] mac)631         public AtMac(byte[] mac) throws EapSimAkaInvalidAttributeException {
632             super(EAP_AT_MAC, ATTR_LENGTH);
633             this.mac = mac;
634 
635             if (mac.length != MAC_LENGTH) {
636                 throw new EapSimAkaInvalidAttributeException("Invalid length for MAC");
637             }
638         }
639 
640         @Override
encode(ByteBuffer byteBuffer)641         public void encode(ByteBuffer byteBuffer) {
642             encodeAttributeHeader(byteBuffer);
643             byteBuffer.put(new byte[RESERVED_BYTES]);
644             byteBuffer.put(mac);
645         }
646     }
647 
648     /**
649      * AtCounter represents the AT_COUNTER attribute defined in RFC 4186#10.15 and RFC 4187#10.16
650      */
651     public static class AtCounter extends EapSimAkaAttribute {
652         private static final int ATTR_LENGTH = LENGTH_SCALING;
653 
654         public final int counter;
655 
AtCounter(int lengthInBytes, ByteBuffer byteBuffer)656         public AtCounter(int lengthInBytes, ByteBuffer byteBuffer)
657                 throws EapSimAkaInvalidAttributeException {
658             super(EAP_AT_COUNTER, lengthInBytes);
659 
660             if (lengthInBytes != ATTR_LENGTH) {
661                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
662             }
663 
664             this.counter = Short.toUnsignedInt(byteBuffer.getShort());
665         }
666 
667         @VisibleForTesting
AtCounter(int counter)668         public AtCounter(int counter) throws EapSimAkaInvalidAttributeException {
669             super(EAP_AT_COUNTER, ATTR_LENGTH);
670             this.counter = counter;
671         }
672 
673         @Override
encode(ByteBuffer byteBuffer)674         public void encode(ByteBuffer byteBuffer) {
675             encodeAttributeHeader(byteBuffer);
676             byteBuffer.putShort((short) counter);
677         }
678     }
679 
680 
681     /**
682      * AtCounterTooSmall represents the AT_COUNTER_TOO_SMALL attribute defined in RFC 4186#10.16 and
683      * RFC 4187#10.17
684      */
685     public static class AtCounterTooSmall extends EapSimAkaAttribute {
686         private static final int ATTR_LENGTH = LENGTH_SCALING;
687         private static final int ATTR_HEADER = 2;
688 
AtCounterTooSmall(int lengthInBytes, ByteBuffer byteBuffer)689         public AtCounterTooSmall(int lengthInBytes, ByteBuffer byteBuffer)
690                 throws EapSimAkaInvalidAttributeException {
691             super(EAP_AT_COUNTER_TOO_SMALL, lengthInBytes);
692 
693             if (lengthInBytes != ATTR_LENGTH) {
694                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
695             }
696             consumePadding(ATTR_HEADER, byteBuffer);
697         }
698 
AtCounterTooSmall()699         public AtCounterTooSmall() throws EapSimAkaInvalidAttributeException {
700             super(EAP_AT_COUNTER_TOO_SMALL, ATTR_LENGTH);
701         }
702 
703         @Override
encode(ByteBuffer byteBuffer)704         public void encode(ByteBuffer byteBuffer) {
705             encodeAttributeHeader(byteBuffer);
706             addPadding(ATTR_HEADER, byteBuffer);
707         }
708     }
709 
710     /**
711      * AtNonceS represents the AT_NONCE_S attribute defined in RFC 4186#10.17 and RFC 4187#10.18
712      *
713      * <p>This Nonce is generated by the server and used for fast re-authentication only.
714      */
715     public static class AtNonceS extends EapSimAkaAttribute {
716         private static final int ATTR_LENGTH = 5 * LENGTH_SCALING;
717         private static final int NONCE_S_LENGTH = 4 * LENGTH_SCALING;
718         private static final int RESERVED_BYTES = 2;
719 
720         public final byte[] nonceS = new byte[NONCE_S_LENGTH];
721 
AtNonceS(int lengthInBytes, ByteBuffer byteBuffer)722         public AtNonceS(int lengthInBytes, ByteBuffer byteBuffer)
723                 throws EapSimAkaInvalidAttributeException {
724             super(EAP_AT_NONCE_S, lengthInBytes);
725 
726             if (lengthInBytes != ATTR_LENGTH) {
727                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
728             }
729 
730             // next two bytes are reserved (RFC 4186 Section 10.17)
731             byteBuffer.get(new byte[RESERVED_BYTES]);
732             byteBuffer.get(nonceS);
733         }
734 
735         @VisibleForTesting
AtNonceS(byte[] nonceS)736         public AtNonceS(byte[] nonceS) throws EapSimAkaInvalidAttributeException {
737             super(EAP_AT_NONCE_S, ATTR_LENGTH);
738 
739             if (nonceS.length != NONCE_S_LENGTH) {
740                 throw new EapSimAkaInvalidAttributeException("NonceS length must be 16B");
741             }
742 
743             System.arraycopy(nonceS, 0, this.nonceS, 0, NONCE_S_LENGTH);
744         }
745 
746         @Override
encode(ByteBuffer byteBuffer)747         public void encode(ByteBuffer byteBuffer) {
748             encodeAttributeHeader(byteBuffer);
749             byteBuffer.put(new byte[RESERVED_BYTES]);
750             byteBuffer.put(nonceS);
751         }
752     }
753 
754     /**
755      * AtNotification represents the AT_NOTIFICATION attribute defined in RFC 4186#10.18 and RFC
756      * 4187#10.19
757      */
758     public static class AtNotification extends EapSimAkaAttribute {
759         private static final int ATTR_LENGTH = 4;
760         private static final int SUCCESS_MASK = 0x8000;
761         private static final int PRE_SUCCESSFUL_CHALLENGE_MASK = 0x4000;
762 
763         // Notification codes defined in RFC 4186 Section 10.18
764         public static final int GENERAL_FAILURE_POST_CHALLENGE = 0;
765         public static final int GENERAL_FAILURE_PRE_CHALLENGE = 16384; // 0x4000
766         public static final int SUCCESS = 32768; // 0x8000
767         public static final int DENIED_ACCESS_POST_CHALLENGE = 1026;
768         public static final int USER_NOT_SUBSCRIBED_POST_CHALLENGE = 1031;
769 
770         private static final Map<Integer, String> CODE_DEFS = loadCodeDefs();
771 
772         public final boolean isSuccessCode;
773         public final boolean isPreSuccessfulChallenge;
774         public final int notificationCode;
775 
AtNotification(int lengthInBytes, ByteBuffer byteBuffer)776         public AtNotification(int lengthInBytes, ByteBuffer byteBuffer)
777                 throws EapSimAkaInvalidAttributeException {
778             super(EAP_AT_NOTIFICATION, lengthInBytes);
779 
780             if (lengthInBytes != ATTR_LENGTH) {
781                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
782             }
783 
784             notificationCode = Short.toUnsignedInt(byteBuffer.getShort());
785 
786             // If Success bit == 0, failure is implied
787             isSuccessCode = (notificationCode & SUCCESS_MASK) != 0;
788 
789             // if Phase bit == 0, notification code can only be used after a successful
790             isPreSuccessfulChallenge = (notificationCode & PRE_SUCCESSFUL_CHALLENGE_MASK) != 0;
791 
792             if (isSuccessCode && isPreSuccessfulChallenge) {
793                 throw new EapSimAkaInvalidAttributeException("Invalid state specified");
794             }
795         }
796 
797         @VisibleForTesting
AtNotification(int notificationCode)798         public AtNotification(int notificationCode) throws EapSimAkaInvalidAttributeException {
799             super(EAP_AT_NOTIFICATION, ATTR_LENGTH);
800             this.notificationCode = notificationCode;
801 
802             // If Success bit == 0, failure is implied
803             isSuccessCode = (notificationCode & SUCCESS_MASK) != 0;
804 
805             // if Phase bit == 0, notification code can only be used after a successful challenge
806             isPreSuccessfulChallenge = (notificationCode & PRE_SUCCESSFUL_CHALLENGE_MASK) != 0;
807 
808             if (isSuccessCode && isPreSuccessfulChallenge) {
809                 throw new EapSimAkaInvalidAttributeException("Invalid state specified");
810             }
811         }
812 
813         @Override
encode(ByteBuffer byteBuffer)814         public void encode(ByteBuffer byteBuffer) {
815             encodeAttributeHeader(byteBuffer);
816             byteBuffer.putShort((short) notificationCode);
817         }
818 
819         @Override
toString()820         public String toString() {
821             String description = CODE_DEFS.getOrDefault(notificationCode, "Code not recognized");
822             return "{Notification Code=" + notificationCode + ", descr=" + description + "}";
823         }
824 
loadCodeDefs()825         private static Map<Integer, String> loadCodeDefs() {
826             Map<Integer, String> defs = new HashMap<>();
827             defs.put(GENERAL_FAILURE_POST_CHALLENGE,
828                     "General failure after authentication. (Implies failure, used after successful"
829                     + " authentication.)");
830             defs.put(GENERAL_FAILURE_PRE_CHALLENGE,
831                     "General failure. (Implies failure, used before authentication.)");
832             defs.put(SUCCESS,
833                     "Success.  User has been successfully authenticated. (Does not imply failure,"
834                     + " used after successful authentication).");
835             defs.put(DENIED_ACCESS_POST_CHALLENGE,
836                     "User has been temporarily denied access to the requested service. (Implies"
837                     + " failure, used after successful authentication.)");
838             defs.put(USER_NOT_SUBSCRIBED_POST_CHALLENGE,
839                     "User has not subscribed to the requested service.  (Implies failure, used"
840                     + " after successful authentication.)");
841             return defs;
842         }
843     }
844 
845     /**
846      * AtClientErrorCode represents the AT_CLIENT_ERROR_CODE attribute defined in RFC 4186#10.19 and
847      * RFC 4187#10.20
848      */
849     public static class AtClientErrorCode extends EapSimAkaAttribute {
850         private static final String TAG = AtClientErrorCode.class.getSimpleName();
851         private static final int ATTR_LENGTH = 4;
852 
853         // Error codes defined in RFC 4186 Section 10.19
854         public static final AtClientErrorCode UNABLE_TO_PROCESS = getClientErrorCode(0);
855         public static final AtClientErrorCode UNSUPPORTED_VERSION = getClientErrorCode(1);
856         public static final AtClientErrorCode INSUFFICIENT_CHALLENGES = getClientErrorCode(2);
857         public static final AtClientErrorCode STALE_RANDS = getClientErrorCode(3);
858 
859         public final int errorCode;
860 
AtClientErrorCode(int lengthInBytes, int errorCode)861         public AtClientErrorCode(int lengthInBytes, int errorCode)
862                 throws EapSimAkaInvalidAttributeException {
863             super(EAP_AT_CLIENT_ERROR_CODE, lengthInBytes);
864 
865             if (lengthInBytes != ATTR_LENGTH) {
866                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
867             }
868 
869             this.errorCode = errorCode;
870         }
871 
872         @Override
encode(ByteBuffer byteBuffer)873         public void encode(ByteBuffer byteBuffer) {
874             encodeAttributeHeader(byteBuffer);
875             byteBuffer.putShort((short) errorCode);
876         }
877 
getClientErrorCode(int errorCode)878         private static AtClientErrorCode getClientErrorCode(int errorCode) {
879             try {
880                 return new AtClientErrorCode(ATTR_LENGTH, errorCode);
881             } catch (EapSimAkaInvalidAttributeException exception) {
882                 LOG.wtf(TAG, "Exception thrown while making AtClientErrorCodeConstants");
883                 return null;
884             }
885         }
886     }
887 
888     /**
889      * AtAutn represents the AT_AUTN attribute defined in RFC 4187#10.7
890      */
891     public static class AtAutn extends EapSimAkaAttribute {
892         private static final int ATTR_LENGTH = 5 * LENGTH_SCALING;
893         private static final int AUTN_LENGTH = 16;
894         private static final int RESERVED_BYTES = 2;
895 
896         public final byte[] autn = new byte[AUTN_LENGTH];
897 
AtAutn(int lengthInBytes, ByteBuffer byteBuffer)898         public AtAutn(int lengthInBytes, ByteBuffer byteBuffer)
899                 throws EapSimAkaInvalidAttributeException {
900             super(EAP_AT_AUTN, lengthInBytes);
901 
902             if (lengthInBytes != ATTR_LENGTH) {
903                 throw new EapSimAkaInvalidAttributeException("Length must be 20B");
904             }
905 
906             // next two bytes are reserved (RFC 4187#10.7)
907             byteBuffer.get(new byte[RESERVED_BYTES]);
908 
909             byteBuffer.get(autn);
910         }
911 
912         @VisibleForTesting
AtAutn(byte[] autn)913         public AtAutn(byte[] autn) throws EapSimAkaInvalidAttributeException {
914             super(EAP_AT_AUTN, ATTR_LENGTH);
915 
916             if (autn.length != AUTN_LENGTH) {
917                 throw new EapSimAkaInvalidAttributeException("Autn must be 16B");
918             }
919 
920             System.arraycopy(autn, 0, this.autn, 0, AUTN_LENGTH);
921         }
922 
923         @Override
encode(ByteBuffer byteBuffer)924         public void encode(ByteBuffer byteBuffer) {
925             encodeAttributeHeader(byteBuffer);
926             byteBuffer.put(new byte[RESERVED_BYTES]);
927             byteBuffer.put(autn);
928         }
929     }
930 
931     /**
932      * AtRes respresents the AT_RES attribute defined in RFC 4187#10.8
933      */
934     public static class AtRes extends EapSimAkaAttribute {
935         private static final int BITS_PER_BYTE = 8;
936         private static final int MIN_RES_LEN_BYTES = 4;
937         private static final int MAX_RES_LEN_BYTES = 16;
938 
939         public final byte[] res;
940 
AtRes(int lengthInBytes, ByteBuffer byteBuffer)941         public AtRes(int lengthInBytes, ByteBuffer byteBuffer)
942                 throws EapSimAkaInvalidAttributeException {
943             super(EAP_AT_RES, lengthInBytes);
944 
945             // RES length is in bits (RFC 4187#10.8).
946             // RES length should be a multiple of 8 bits (TS 133 105#5.1.7.8)
947             int resLength = Short.toUnsignedInt(byteBuffer.getShort());
948             if (resLength % BITS_PER_BYTE != 0) {
949                 throw new EapSimAkaInvalidAttributeException("RES length must be multiple of 8");
950             }
951             int resLengthBytes = resLength / BITS_PER_BYTE;
952             if (resLengthBytes < MIN_RES_LEN_BYTES || resLengthBytes > MAX_RES_LEN_BYTES) {
953                 throw new EapSimAkaInvalidAttributeException(
954                         "RES length must be: 4B <= len <= 16B");
955             }
956 
957             res = new byte[resLengthBytes];
958             byteBuffer.get(res);
959 
960             int bytesUsed = MIN_ATTR_LENGTH + resLengthBytes;
961             consumePadding(bytesUsed, byteBuffer);
962         }
963 
964         @VisibleForTesting
AtRes(int lengthInBytes, byte[] res)965         public AtRes(int lengthInBytes, byte[] res) throws EapSimAkaInvalidAttributeException {
966             super(EAP_AT_RES, lengthInBytes);
967 
968             if (res.length < MIN_RES_LEN_BYTES || res.length > MAX_RES_LEN_BYTES) {
969                 throw new EapSimAkaInvalidAttributeException(
970                         "RES length must be: 4B <= len <= 16B");
971             }
972 
973             this.res = res;
974         }
975 
976         @Override
encode(ByteBuffer byteBuffer)977         public void encode(ByteBuffer byteBuffer) {
978             encodeAttributeHeader(byteBuffer);
979 
980             int resLenBits = res.length * BITS_PER_BYTE;
981             byteBuffer.putShort((short) resLenBits);
982             byteBuffer.put(res);
983 
984             int bytesUsed = MIN_ATTR_LENGTH + res.length;
985             addPadding(bytesUsed, byteBuffer);
986         }
987 
988         /**
989          * Creates and returns an AtRes instance with the given res value.
990          *
991          * @param res byte-array RES value to be used for this
992          * @return AtRes instance for the given RES value
993          * @throws EapSimAkaInvalidAttributeException if the given res value has an invalid length
994          */
getAtRes(byte[] res)995         public static AtRes getAtRes(byte[] res) throws EapSimAkaInvalidAttributeException {
996             // Attributes must be 4B-aligned, so there can be 0 to 3 padding bytes added
997             int resLenBytes = MIN_ATTR_LENGTH + res.length;
998             if (resLenBytes % LENGTH_SCALING != 0) {
999                 resLenBytes += LENGTH_SCALING - (resLenBytes % LENGTH_SCALING);
1000             }
1001 
1002             return new AtRes(resLenBytes, res);
1003         }
1004 
1005         /**
1006          * Checks whether the given RES length is valid.
1007          *
1008          * @param resLenBytes the RES length to be checked
1009          * @return true iff the given resLen is valid
1010          */
isValidResLen(int resLenBytes)1011         public static boolean isValidResLen(int resLenBytes) {
1012             return resLenBytes >= MIN_RES_LEN_BYTES && resLenBytes <= MAX_RES_LEN_BYTES;
1013         }
1014     }
1015 
1016     /**
1017      * AtAuts represents the AT_AUTS attribute defined in RFC 4187#10.9
1018      */
1019     public static class AtAuts extends EapSimAkaAttribute {
1020         private static final int ATTR_LENGTH = 4 * LENGTH_SCALING;
1021         public static final int AUTS_LENGTH = 14;
1022 
1023         public final byte[] auts = new byte[AUTS_LENGTH];
1024 
AtAuts(int lengthInBytes, ByteBuffer byteBuffer)1025         public AtAuts(int lengthInBytes, ByteBuffer byteBuffer)
1026                 throws EapSimAkaInvalidAttributeException {
1027             super(EAP_AT_AUTS, lengthInBytes);
1028 
1029             if (lengthInBytes != ATTR_LENGTH) {
1030                 throw new EapSimAkaInvalidAttributeException("Length must be 16B");
1031             }
1032 
1033             byteBuffer.get(auts);
1034         }
1035 
AtAuts(byte[] auts)1036         public AtAuts(byte[] auts) throws EapSimAkaInvalidAttributeException {
1037             super(EAP_AT_AUTS, ATTR_LENGTH);
1038 
1039             if (auts.length != AUTS_LENGTH) {
1040                 throw new EapSimAkaInvalidAttributeException("Auts must be 14B");
1041             }
1042 
1043             System.arraycopy(auts, 0, this.auts, 0, AUTS_LENGTH);
1044         }
1045 
1046         @Override
encode(ByteBuffer byteBuffer)1047         public void encode(ByteBuffer byteBuffer) {
1048             encodeAttributeHeader(byteBuffer);
1049 
1050             byteBuffer.put(auts);
1051         }
1052     }
1053 
1054     /**
1055      * AtKdfInput represents the AT_KDF_INPUT attribute defined in RFC 5448#3.1
1056      */
1057     public static class AtKdfInput extends EapSimAkaAttribute {
1058         public final byte[] networkName;
1059 
AtKdfInput(int lengthInBytes, ByteBuffer byteBuffer)1060         public AtKdfInput(int lengthInBytes, ByteBuffer byteBuffer)
1061                 throws EapSimAkaInvalidAttributeException {
1062             super(EAP_AT_KDF_INPUT, lengthInBytes);
1063 
1064             int networkNameLength = Short.toUnsignedInt(byteBuffer.getShort());
1065             networkName = new byte[networkNameLength];
1066             byteBuffer.get(networkName);
1067 
1068             int bytesUsed = MIN_ATTR_LENGTH + networkNameLength;
1069             consumePadding(bytesUsed, byteBuffer);
1070         }
1071 
1072         @VisibleForTesting
AtKdfInput(int lengthInbytes, byte[] networkName)1073         public AtKdfInput(int lengthInbytes, byte[] networkName)
1074                 throws EapSimAkaInvalidAttributeException {
1075             super(EAP_AT_KDF_INPUT, lengthInbytes);
1076 
1077             this.networkName = networkName;
1078         }
1079 
1080         @Override
encode(ByteBuffer byteBuffer)1081         public void encode(ByteBuffer byteBuffer) {
1082             encodeAttributeHeader(byteBuffer);
1083             byteBuffer.putShort((short) networkName.length);
1084             byteBuffer.put(networkName);
1085 
1086             int bytesUsed = MIN_ATTR_LENGTH + networkName.length;
1087             addPadding(bytesUsed, byteBuffer);
1088         }
1089     }
1090 
1091     /**
1092      * AdKdf represents the AT_KDF attribute defined in RFC 5448#3.2
1093      */
1094     public static class AtKdf extends EapSimAkaAttribute {
1095         private static final int ATTR_LENGTH = MIN_ATTR_LENGTH;
1096 
1097         public final int kdf;
1098 
AtKdf(int lengthInBytes, ByteBuffer buffer)1099         public AtKdf(int lengthInBytes, ByteBuffer buffer)
1100                 throws EapSimAkaInvalidAttributeException {
1101             super(EAP_AT_KDF, lengthInBytes);
1102 
1103             if (lengthInBytes != ATTR_LENGTH) {
1104                 throw new EapSimAkaInvalidAttributeException("AtKdf length must be 4B");
1105             }
1106 
1107             kdf = Short.toUnsignedInt(buffer.getShort());
1108         }
1109 
1110         @VisibleForTesting
AtKdf(int kdf)1111         public AtKdf(int kdf) throws EapSimAkaInvalidAttributeException {
1112             super(EAP_AT_KDF, ATTR_LENGTH);
1113 
1114             this.kdf = kdf;
1115         }
1116 
1117         @Override
encode(ByteBuffer byteBuffer)1118         public void encode(ByteBuffer byteBuffer) {
1119             encodeAttributeHeader(byteBuffer);
1120 
1121             byteBuffer.putShort((short) kdf);
1122         }
1123     }
1124 
1125     /**
1126      * AtBidding represents the AT_BIDDING attribute defined in RFC 5448#4
1127      */
1128     public static class AtBidding extends EapSimAkaAttribute {
1129         private static final int ATTR_LENGTH = MIN_ATTR_LENGTH;
1130         private static final int SUPPORTS_EAP_AKA_PRIME_MASK = 0x8000;
1131 
1132         public final boolean doesServerSupportEapAkaPrime;
1133 
AtBidding(int lengthInBytes, ByteBuffer buffer)1134         public AtBidding(int lengthInBytes, ByteBuffer buffer)
1135                 throws EapSimAkaInvalidAttributeException {
1136             super(EAP_AT_BIDDING, lengthInBytes);
1137 
1138             if (lengthInBytes != ATTR_LENGTH) {
1139                 throw new EapSimAkaInvalidAttributeException("AtBidding length must be 4B");
1140             }
1141 
1142             int serverFlag = Short.toUnsignedInt(buffer.getShort());
1143             doesServerSupportEapAkaPrime = (serverFlag & SUPPORTS_EAP_AKA_PRIME_MASK) != 0;
1144         }
1145 
1146         @VisibleForTesting
AtBidding(boolean doesServerSupportEapAkaPrime)1147         public AtBidding(boolean doesServerSupportEapAkaPrime)
1148                 throws EapSimAkaInvalidAttributeException {
1149             super(EAP_AT_BIDDING, ATTR_LENGTH);
1150 
1151             this.doesServerSupportEapAkaPrime = doesServerSupportEapAkaPrime;
1152         }
1153 
1154         @Override
encode(ByteBuffer byteBuffer)1155         public void encode(ByteBuffer byteBuffer) {
1156             encodeAttributeHeader(byteBuffer);
1157 
1158             int flagToWrite = doesServerSupportEapAkaPrime ? SUPPORTS_EAP_AKA_PRIME_MASK : 0;
1159             byteBuffer.putShort((short) flagToWrite);
1160         }
1161     }
1162 }
1163