aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
blob: 27fb96517eb8b291485caba066b68355f01679f1 (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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.net.ipsec.ike.message;

import static android.net.ipsec.ike.IkeManager.getIkeLog;

import static com.android.internal.net.ipsec.ike.message.IkePayload.PayloadType;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeInternalException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.util.Pair;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord;
import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
import com.android.internal.net.ipsec.ike.exceptions.InvalidMessageIdException;
import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
import com.android.internal.net.ipsec.ike.exceptions.UnsupportedCriticalPayloadException;
import com.android.org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * IkeMessage represents an IKE message.
 *
 * <p>It contains all attributes and provides methods for encoding, decoding, encrypting and
 * decrypting.
 *
 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3">RFC 7296, Internet Key Exchange
 *     Protocol Version 2 (IKEv2)</a>
 */
public final class IkeMessage {
    private static final String TAG = "IkeMessage";

    private static IIkeMessageHelper sIkeMessageHelper = new IkeMessageHelper();

    // Currently use Bouncy Castle as crypto security provider
    static final Provider SECURITY_PROVIDER = new BouncyCastleProvider();

    // TODO: b/142070035 Use Conscrypt as default security provider instead of BC

    // Currently use HarmonyJSSE as TrustManager provider
    static final Provider TRUST_MANAGER_PROVIDER = Security.getProvider("HarmonyJSSE");

    // Payload types in this set may be included multiple times within an IKE message. All other
    // payload types can be included at most once.
    private static final Set<Integer> REPEATABLE_PAYLOAD_TYPES = new HashSet<>();

    static {
        REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_CERT);
        REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_CERT_REQUEST);
        REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
        REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_DELETE);
        REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_VENDOR);
    }

    public final IkeHeader ikeHeader;
    public final List<IkePayload> ikePayloadList;
    /**
     * Conctruct an instance of IkeMessage. It is called by decode or for building outbound message.
     *
     * @param header the header of this IKE message
     * @param payloadList the list of decoded IKE payloads in this IKE message
     */
    public IkeMessage(IkeHeader header, List<IkePayload> payloadList) {
        ikeHeader = header;
        ikePayloadList = payloadList;
    }

    /**
     * Get security provider for IKE library
     *
     * <p>Use BouncyCastleProvider as the default security provider.
     *
     * @return the security provider of IKE library.
     */
    public static Provider getSecurityProvider() {
        // TODO: Move this getter out of IKE message package since not only this package uses it.
        return SECURITY_PROVIDER;
    }

    /**
     * Get security provider for X509TrustManager to do certificate validation.
     *
     * <p>Use JSSEProvdier as the default security provider.
     *
     * @return the provider for X509TrustManager
     */
    public static Provider getTrustManagerProvider() {
        return TRUST_MANAGER_PROVIDER;
    }

    /**
     * Decode unencrypted IKE message body and create an instance of IkeMessage.
     *
     * <p>This method catches all RuntimeException during decoding incoming IKE packet.
     *
     * @param expectedMsgId the expected message ID to validate against.
     * @param header the IKE header that is decoded but not validated.
     * @param inputPacket the byte array contains the whole IKE message.
     * @return the decoding result.
     */
    public static DecodeResult decode(int expectedMsgId, IkeHeader header, byte[] inputPacket) {
        return sIkeMessageHelper.decode(expectedMsgId, header, inputPacket);
    }

    /**
     * Decrypt and decode encrypted IKE message body and create an instance of IkeMessage.
     *
     * @param expectedMsgId the expected message ID to validate against.
     * @param integrityMac the negotiated integrity algorithm.
     * @param decryptCipher the negotiated encryption algorithm.
     * @param ikeSaRecord ikeSaRecord where this packet is sent on.
     * @param ikeHeader header of IKE packet.
     * @param packet IKE packet as a byte array.
     * @param collectedFragments previously received IKE fragments.
     * @return the decoding result.
     */
    public static DecodeResult decode(
            int expectedMsgId,
            @Nullable IkeMacIntegrity integrityMac,
            IkeCipher decryptCipher,
            IkeSaRecord ikeSaRecord,
            IkeHeader ikeHeader,
            byte[] packet,
            DecodeResultPartial collectedFragments) {
        return sIkeMessageHelper.decode(
                expectedMsgId,
                integrityMac,
                decryptCipher,
                ikeSaRecord,
                ikeHeader,
                packet,
                collectedFragments);
    }

    private static List<IkePayload> decodePayloadList(
            @PayloadType int firstPayloadType, boolean isResp, byte[] unencryptedPayloads)
            throws IkeProtocolException {
        ByteBuffer inputBuffer = ByteBuffer.wrap(unencryptedPayloads);
        int currentPayloadType = firstPayloadType;
        // For supported payload
        List<IkePayload> supportedPayloadList = new LinkedList<>();
        // For unsupported critical payload
        List<Integer> unsupportedCriticalPayloadList = new LinkedList<>();

        // For marking the existence of supported payloads in this message.
        HashSet<Integer> supportedTypesFoundSet = new HashSet<>();

        StringBuilder logPayloadsSb = new StringBuilder();
        logPayloadsSb.append("Decoded payloads [ ");

        while (currentPayloadType != IkePayload.PAYLOAD_TYPE_NO_NEXT) {
            Pair<IkePayload, Integer> pair =
                    IkePayloadFactory.getIkePayload(currentPayloadType, isResp, inputBuffer);
            IkePayload payload = pair.first;
            logPayloadsSb.append(payload.getTypeString()).append(" ");

            if (!(payload instanceof IkeUnsupportedPayload)) {
                int type = payload.payloadType;
                if (!supportedTypesFoundSet.add(type) && !REPEATABLE_PAYLOAD_TYPES.contains(type)) {
                    throw new InvalidSyntaxException(
                            "It is not allowed to have multiple payloads with payload type: "
                                    + type);
                }

                supportedPayloadList.add(payload);
            } else if (payload.isCritical) {
                unsupportedCriticalPayloadList.add(payload.payloadType);
            }
            // Simply ignore unsupported uncritical payload.

            currentPayloadType = pair.second;
        }

        logPayloadsSb.append("]");
        getIkeLog().d("IkeMessage", logPayloadsSb.toString());

        if (inputBuffer.remaining() > 0) {
            throw new InvalidSyntaxException(
                    "Malformed IKE Payload: Unexpected bytes at the end of packet.");
        }

        if (unsupportedCriticalPayloadList.size() > 0) {
            throw new UnsupportedCriticalPayloadException(unsupportedCriticalPayloadList);
        }

        // TODO: Verify that for all status notification payloads, only
        // NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP and NOTIFY_TYPE_IPCOMP_SUPPORTED can be included
        // multiple times in a request message. There is not a clear number restriction for
        // error notification payloads.

        return supportedPayloadList;
    }

    /**
     * Encode unencrypted IKE message.
     *
     * @return encoded IKE message in byte array.
     */
    public byte[] encode() {
        return sIkeMessageHelper.encode(this);
    }

    /**
     * Encrypt and encode packet.
     *
     * @param integrityMac the negotiated integrity algorithm.
     * @param encryptCipher the negotiated encryption algortihm.
     * @param ikeSaRecord the ikeSaRecord where this packet is sent on.
     * @param supportFragment if IKE fragmentation is supported
     * @param fragSize the maximum size of IKE fragment
     * @return encoded IKE message in byte array.
     */
    public byte[][] encryptAndEncode(
            @Nullable IkeMacIntegrity integrityMac,
            IkeCipher encryptCipher,
            IkeSaRecord ikeSaRecord,
            boolean supportFragment,
            int fragSize) {
        return sIkeMessageHelper.encryptAndEncode(
                integrityMac, encryptCipher, ikeSaRecord, this, supportFragment, fragSize);
    }

    /**
     * Encode all payloads to a byte array.
     *
     * @return byte array contains all encoded payloads
     */
    private byte[] encodePayloads() {
        StringBuilder logPayloadsSb = new StringBuilder();
        logPayloadsSb.append("Generating payloads [ ");

        int payloadLengthSum = 0;
        for (IkePayload payload : ikePayloadList) {
            payloadLengthSum += payload.getPayloadLength();
            logPayloadsSb.append(payload.getTypeString()).append(" ");
        }
        logPayloadsSb.append("]");
        getIkeLog().d("IkeMessage", logPayloadsSb.toString());

        if (ikePayloadList.isEmpty()) return new byte[0];

        ByteBuffer byteBuffer = ByteBuffer.allocate(payloadLengthSum);
        for (int i = 0; i < ikePayloadList.size() - 1; i++) {
            ikePayloadList
                    .get(i)
                    .encodeToByteBuffer(ikePayloadList.get(i + 1).payloadType, byteBuffer);
        }
        ikePayloadList
                .get(ikePayloadList.size() - 1)
                .encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, byteBuffer);

        return byteBuffer.array();
    }

    /** Package */
    @VisibleForTesting
    byte[] attachEncodedHeader(byte[] encodedIkeBody) {
        ByteBuffer outputBuffer =
                ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + encodedIkeBody.length);
        ikeHeader.encodeToByteBuffer(outputBuffer, encodedIkeBody.length);
        outputBuffer.put(encodedIkeBody);
        return outputBuffer.array();
    }

    /**
     * Obtain all payloads with input payload type.
     *
     * <p>This method can be only applied to the payload types that can be included multiple times
     * within an IKE message.
     *
     * @param payloadType the payloadType to look for.
     * @param payloadClass the class of the desired payloads.
     * @return a list of IkePayloads with the payloadType.
     */
    public <T extends IkePayload> List<T> getPayloadListForType(
            @IkePayload.PayloadType int payloadType, Class<T> payloadClass) {
        // STOPSHIP: b/130190639 Notify user the error and close IKE session.
        if (!REPEATABLE_PAYLOAD_TYPES.contains(payloadType)) {
            throw new IllegalArgumentException(
                    "Received unexpected payloadType: "
                            + payloadType
                            + " that can be included at most once within an IKE message.");
        }

        return IkePayload.getPayloadListForTypeInProvidedList(
                payloadType, payloadClass, ikePayloadList);
    }

    /**
     * Obtain the payload with the input payload type.
     *
     * <p>This method can be only applied to the payload type that can be included at most once
     * within an IKE message.
     *
     * @param payloadType the payloadType to look for.
     * @param payloadClass the class of the desired payload.
     * @return the IkePayload with the payloadType.
     */
    public <T extends IkePayload> T getPayloadForType(
            @IkePayload.PayloadType int payloadType, Class<T> payloadClass) {
        // STOPSHIP: b/130190639 Notify user the error and close IKE session.
        if (REPEATABLE_PAYLOAD_TYPES.contains(payloadType)) {
            throw new IllegalArgumentException(
                    "Received unexpected payloadType: "
                            + payloadType
                            + " that may be included multiple times within an IKE message.");
        }

        return IkePayload.getPayloadForTypeInProvidedList(
                payloadType, payloadClass, ikePayloadList);
    }

    /**
     * Checks if this Request IkeMessage was a DPD message
     *
     * <p>An IKE message is a DPD request iff the message was encrypted (has a SK payload) and there
     * were no payloads within the SK payload (or outside the SK payload).
     */
    public boolean isDpdRequest() {
        return !ikeHeader.isResponseMsg
                && ikeHeader.exchangeType == IkeHeader.EXCHANGE_TYPE_INFORMATIONAL
                && ikePayloadList.isEmpty()
                && ikeHeader.nextPayloadType == IkePayload.PAYLOAD_TYPE_SK;
    }

    /**
     * IIkeMessageHelper provides interface for decoding, encoding and processing IKE packet.
     *
     * <p>IkeMessageHelper exists so that the interface is injectable for testing.
     */
    @VisibleForTesting
    public interface IIkeMessageHelper {
        /**
         * Encode IKE message.
         *
         * @param ikeMessage message need to be encoded.
         * @return encoded IKE message in byte array.
         */
        byte[] encode(IkeMessage ikeMessage);

        /**
         * Encrypt and encode IKE message.
         *
         * @param integrityMac the negotiated integrity algorithm.
         * @param encryptCipher the negotiated encryption algortihm.
         * @param ikeSaRecord the ikeSaRecord where this packet is sent on.
         * @param ikeMessage message need to be encoded. * @param supportFragment if IKE
         *     fragmentation is supported.
         * @param fragSize the maximum size of IKE fragment.
         * @return encoded IKE message in byte array.
         */
        byte[][] encryptAndEncode(
                @Nullable IkeMacIntegrity integrityMac,
                IkeCipher encryptCipher,
                IkeSaRecord ikeSaRecord,
                IkeMessage ikeMessage,
                boolean supportFragment,
                int fragSize);

        // TODO: Return DecodeResult when decoding unencrypted message
        /**
         * Decode unencrypted packet.
         *
         * @param expectedMsgId the expected message ID to validate against.
         * @param ikeHeader header of IKE packet.
         * @param packet IKE packet as a byte array.
         * @return the decoding result.
         */
        DecodeResult decode(int expectedMsgId, IkeHeader ikeHeader, byte[] packet);

        /**
         * Decrypt and decode packet.
         *
         * @param expectedMsgId the expected message ID to validate against.
         * @param integrityMac the negotiated integrity algorithm.
         * @param decryptCipher the negotiated encryption algorithm.
         * @param ikeSaRecord ikeSaRecord where this packet is sent on.
         * @param ikeHeader header of IKE packet.
         * @param packet IKE packet as a byte array.
         * @param collectedFragments previously received IKE fragments.
         * @return the decoding result.
         */
        DecodeResult decode(
                int expectedMsgId,
                @Nullable IkeMacIntegrity integrityMac,
                IkeCipher decryptCipher,
                IkeSaRecord ikeSaRecord,
                IkeHeader ikeHeader,
                byte[] packet,
                DecodeResultPartial collectedFragments);
    }

    /** IkeMessageHelper provides methods for decoding, encoding and processing IKE packet. */
    public static final class IkeMessageHelper implements IIkeMessageHelper {
        @Override
        public byte[] encode(IkeMessage ikeMessage) {
            getIkeLog().d("IkeMessage", "Generating " + ikeMessage.ikeHeader.getBasicInfoString());

            byte[] encodedIkeBody = ikeMessage.encodePayloads();
            byte[] packet = ikeMessage.attachEncodedHeader(encodedIkeBody);
            getIkeLog().d("IkeMessage", "Build a complete IKE message: " + getIkeLog().pii(packet));
            return packet;
        }

        @Override
        public byte[][] encryptAndEncode(
                @Nullable IkeMacIntegrity integrityMac,
                IkeCipher encryptCipher,
                IkeSaRecord ikeSaRecord,
                IkeMessage ikeMessage,
                boolean supportFragment,
                int fragSize) {
            getIkeLog().d("IkeMessage", "Generating " + ikeMessage.ikeHeader.getBasicInfoString());

            return encryptAndEncode(
                    ikeMessage.ikeHeader,
                    ikeMessage.ikePayloadList.isEmpty()
                            ? IkePayload.PAYLOAD_TYPE_NO_NEXT
                            : ikeMessage.ikePayloadList.get(0).payloadType,
                    ikeMessage.encodePayloads(),
                    integrityMac,
                    encryptCipher,
                    ikeSaRecord.getOutboundIntegrityKey(),
                    ikeSaRecord.getOutboundEncryptionKey(),
                    supportFragment,
                    fragSize);
        }

        @VisibleForTesting
        byte[][] encryptAndEncode(
                IkeHeader ikeHeader,
                @PayloadType int firstInnerPayload,
                byte[] unencryptedPayloads,
                @Nullable IkeMacIntegrity integrityMac,
                IkeCipher encryptCipher,
                byte[] integrityKey,
                byte[] encryptionKey,
                boolean supportFragment,
                int fragSize) {

            IkeSkPayload skPayload =
                    new IkeSkPayload(
                            ikeHeader,
                            firstInnerPayload,
                            unencryptedPayloads,
                            integrityMac,
                            encryptCipher,
                            integrityKey,
                            encryptionKey);
            int msgLen = IkeHeader.IKE_HEADER_LENGTH + skPayload.getPayloadLength();

            // Build complete IKE message
            if (!supportFragment || msgLen <= fragSize) {
                byte[][] packetList = new byte[1][];
                packetList[0] = encodeHeaderAndBody(ikeHeader, skPayload, firstInnerPayload);

                getIkeLog()
                        .d(
                                "IkeMessage",
                                "Build a complete IKE message: " + getIkeLog().pii(packetList[0]));
                return packetList;
            }

            // Build IKE fragments
            int dataLenPerPacket =
                    fragSize
                            - IkeHeader.IKE_HEADER_LENGTH
                            - IkePayload.GENERIC_HEADER_LENGTH
                            - IkeSkfPayload.SKF_HEADER_LEN
                            - encryptCipher.getIvLen()
                            - integrityMac.getChecksumLen()
                            - encryptCipher.getBlockSize();

            // Caller of this method MUST validate fragSize is valid.
            if (dataLenPerPacket <= 0) {
                throw new IllegalArgumentException(
                        "Max fragment size is too small for an IKE fragment.");
            }

            int totalFragments =
                    (unencryptedPayloads.length + dataLenPerPacket - 1) / dataLenPerPacket;
            IkeHeader skfHeader = ikeHeader.makeSkfHeaderFromSkHeader();
            byte[][] packetList = new byte[totalFragments][];

            ByteBuffer unencryptedDataBuffer = ByteBuffer.wrap(unencryptedPayloads);
            for (int i = 0; i < totalFragments; i++) {
                byte[] unencryptedData =
                        new byte[Math.min(dataLenPerPacket, unencryptedDataBuffer.remaining())];
                unencryptedDataBuffer.get(unencryptedData);

                int fragNum = i + 1; // 1-based

                IkeSkfPayload skfPayload =
                        new IkeSkfPayload(
                                ikeHeader,
                                firstInnerPayload,
                                unencryptedData,
                                integrityMac,
                                encryptCipher,
                                integrityKey,
                                encryptionKey,
                                fragNum,
                                totalFragments);

                packetList[i] =
                        encodeHeaderAndBody(
                                skfHeader,
                                skfPayload,
                                i == 0 ? firstInnerPayload : IkePayload.PAYLOAD_TYPE_NO_NEXT);
                getIkeLog()
                        .d(
                                "IkeMessage",
                                "Build an IKE fragment ("
                                        + (i + 1)
                                        + "/"
                                        + totalFragments
                                        + "): "
                                        + getIkeLog().pii(packetList[0]));
            }

            return packetList;
        }

        private byte[] encodeHeaderAndBody(
                IkeHeader ikeHeader, IkeSkPayload skPayload, @PayloadType int firstInnerPayload) {
            ByteBuffer outputBuffer =
                    ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + skPayload.getPayloadLength());
            ikeHeader.encodeToByteBuffer(outputBuffer, skPayload.getPayloadLength());
            skPayload.encodeToByteBuffer(firstInnerPayload, outputBuffer);
            return outputBuffer.array();
        }

        @Override
        public DecodeResult decode(int expectedMsgId, IkeHeader header, byte[] inputPacket) {
            try {
                if (header.messageId != expectedMsgId) {
                    throw new InvalidMessageIdException(header.messageId);
                }

                header.validateMajorVersion();
                header.validateInboundHeader(inputPacket.length);

                byte[] unencryptedPayloads =
                        Arrays.copyOfRange(
                                inputPacket, IkeHeader.IKE_HEADER_LENGTH, inputPacket.length);
                List<IkePayload> supportedPayloadList =
                        decodePayloadList(
                                header.nextPayloadType, header.isResponseMsg, unencryptedPayloads);
                return new DecodeResultOk(
                        new IkeMessage(header, supportedPayloadList), inputPacket);
            } catch (NegativeArraySizeException | BufferUnderflowException e) {
                // Invalid length error when parsing payload bodies.
                return new DecodeResultUnprotectedError(
                        new InvalidSyntaxException("Malformed IKE Payload"));
            } catch (IkeProtocolException e) {
                return new DecodeResultUnprotectedError(e);
            }
        }

        @Override
        public DecodeResult decode(
                int expectedMsgId,
                @Nullable IkeMacIntegrity integrityMac,
                IkeCipher decryptCipher,
                IkeSaRecord ikeSaRecord,
                IkeHeader ikeHeader,
                byte[] packet,
                DecodeResultPartial collectedFragments) {
            return decode(
                    expectedMsgId,
                    ikeHeader,
                    packet,
                    integrityMac,
                    decryptCipher,
                    ikeSaRecord.getInboundIntegrityKey(),
                    ikeSaRecord.getInboundDecryptionKey(),
                    collectedFragments);
        }

        private DecodeResult decode(
                int expectedMsgId,
                IkeHeader header,
                byte[] inputPacket,
                @Nullable IkeMacIntegrity integrityMac,
                IkeCipher decryptCipher,
                byte[] integrityKey,
                byte[] decryptionKey,
                DecodeResultPartial collectedFragments) {
            if (header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SK
                    && header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SKF) {
                // TODO: b/123372339 Handle message containing unprotected payloads.
                throw new UnsupportedOperationException("Message contains unprotected payloads");
            }

            // Decrypt message and do authentication
            Pair<IkeSkPayload, Integer> pair;
            try {
                pair =
                        decryptAndAuthenticate(
                                expectedMsgId,
                                header,
                                inputPacket,
                                integrityMac,
                                decryptCipher,
                                integrityKey,
                                decryptionKey);
            } catch (IkeException e) {
                if (collectedFragments == null) {
                    return new DecodeResultUnprotectedError(e);
                } else {
                    getIkeLog()
                            .i(
                                    TAG,
                                    "Message authentication or decryption failed on received"
                                            + " message. Discard it ",
                                    e);
                    return collectedFragments;
                }
            }

            // Handle IKE fragment
            boolean isFragment = (header.nextPayloadType == IkePayload.PAYLOAD_TYPE_SKF);
            boolean fragReassemblyStarted = (collectedFragments != null);

            if (isFragment) {
                getIkeLog()
                        .d(
                                TAG,
                                "Received an IKE fragment ("
                                        + ((IkeSkfPayload) pair.first).fragmentNum
                                        + "/"
                                        + ((IkeSkfPayload) pair.first).totalFragments
                                        + ")");
            }

            // IKE fragment reassembly has started but a complete message was received.
            if (!isFragment && fragReassemblyStarted) {
                getIkeLog()
                        .w(
                                TAG,
                                "Received a complete IKE message while doing IKE fragment"
                                        + " reassembly. Discard the newly received message.");
                return collectedFragments;
            }

            byte[] firstPacket = inputPacket;
            byte[] decryptedBytes = pair.first.getUnencryptedData();
            int firstPayloadType = pair.second;

            // Received an IKE fragment
            if (isFragment) {
                validateFragmentHeader(header, inputPacket.length, collectedFragments);

                // Add the recently received fragment to the reassembly queue.
                DecodeResultPartial DecodeResultPartial =
                        processIkeFragment(
                                header,
                                inputPacket,
                                (IkeSkfPayload) (pair.first),
                                pair.second,
                                collectedFragments);

                if (!DecodeResultPartial.isAllFragmentsReceived()) return DecodeResultPartial;

                firstPayloadType = DecodeResultPartial.firstPayloadType;
                decryptedBytes = DecodeResultPartial.reassembleAllFrags();
                firstPacket = DecodeResultPartial.firstFragBytes;
            }

            // Received or has reassembled a complete IKE message. Check if there is protocol error.
            try {
                // TODO: Log IKE header information and payload types

                List<IkePayload> supportedPayloadList =
                        decodePayloadList(firstPayloadType, header.isResponseMsg, decryptedBytes);

                header.validateInboundHeader(inputPacket.length);
                return new DecodeResultOk(
                        new IkeMessage(header, supportedPayloadList), firstPacket);
            } catch (NegativeArraySizeException | BufferUnderflowException e) {
                // Invalid length error when parsing payload bodies.
                return new DecodeResultProtectedError(
                        new InvalidSyntaxException("Malformed IKE Payload", e), firstPacket);
            } catch (IkeProtocolException e) {
                return new DecodeResultProtectedError(e, firstPacket);
            }
        }

        private Pair<IkeSkPayload, Integer> decryptAndAuthenticate(
                int expectedMsgId,
                IkeHeader header,
                byte[] inputPacket,
                @Nullable IkeMacIntegrity integrityMac,
                IkeCipher decryptCipher,
                byte[] integrityKey,
                byte[] decryptionKey)
                throws IkeException {

            try {
                if (header.messageId != expectedMsgId) {
                    throw new InvalidMessageIdException(header.messageId);
                }

                header.validateMajorVersion();

                boolean isSkf = header.nextPayloadType == IkePayload.PAYLOAD_TYPE_SKF;
                return IkePayloadFactory.getIkeSkPayload(
                        isSkf,
                        inputPacket,
                        integrityMac,
                        decryptCipher,
                        integrityKey,
                        decryptionKey);
            } catch (NegativeArraySizeException | BufferUnderflowException e) {
                throw new InvalidSyntaxException("Malformed IKE Payload", e);
            } catch (GeneralSecurityException e) {
                throw new IkeInternalException(e);
            }
        }

        private void validateFragmentHeader(
                IkeHeader fragIkeHeader, int packetLen, DecodeResultPartial collectedFragments) {
            try {
                fragIkeHeader.validateInboundHeader(packetLen);
            } catch (IkeProtocolException e) {
                getIkeLog()
                        .e(
                                TAG,
                                "Received an IKE fragment with invalid header. Will be handled when"
                                        + " reassembly is done.",
                                e);
            }

            if (collectedFragments == null) return;
            if (fragIkeHeader.exchangeType != collectedFragments.ikeHeader.exchangeType) {
                getIkeLog()
                        .e(
                                TAG,
                                "Received an IKE fragment with different exchange type from"
                                        + " previously collected fragments. Ignore it.");
            }
        }

        private DecodeResultPartial processIkeFragment(
                IkeHeader header,
                byte[] inputPacket,
                IkeSkfPayload skf,
                int nextPayloadType,
                @Nullable DecodeResultPartial collectedFragments) {
            if (collectedFragments == null) {
                return new DecodeResultPartial(
                        header, inputPacket, skf, nextPayloadType, collectedFragments);
            }

            if (skf.totalFragments > collectedFragments.collectedFragsList.length) {
                getIkeLog()
                        .i(
                                TAG,
                                "Received IKE fragment has larger total fragments number. Discard"
                                        + " all previously collected fragments");
                return new DecodeResultPartial(
                        header, inputPacket, skf, nextPayloadType, null /*collectedFragments*/);
            }

            if (skf.totalFragments < collectedFragments.collectedFragsList.length) {
                getIkeLog()
                        .i(
                                TAG,
                                "Received IKE fragment has smaller total fragments number. Discard"
                                        + " it.");
                return collectedFragments;
            }

            if (collectedFragments.collectedFragsList[skf.fragmentNum - 1] != null) {
                getIkeLog().i(TAG, "Received IKE fragment is a replay.");
                return collectedFragments;
            }

            return new DecodeResultPartial(
                    header, inputPacket, skf, nextPayloadType, collectedFragments);
        }
    }

    /** Status to describe the result of decoding an inbound IKE message. */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
        DECODE_STATUS_OK,
        DECODE_STATUS_PARTIAL,
        DECODE_STATUS_PROTECTED_ERROR,
        DECODE_STATUS_UNPROTECTED_ERROR,
    })
    public @interface DecodeStatus {}

    /**
     * Represents a message that has been successfully (decrypted and) decoded or reassembled from
     * IKE fragments
     */
    public static final int DECODE_STATUS_OK = 0;
    /** Represents that reassembly process of IKE fragments has started but has not finished */
    public static final int DECODE_STATUS_PARTIAL = 1;
    /** Represents a crypto protected message with correct message ID but has parsing error. */
    public static final int DECODE_STATUS_PROTECTED_ERROR = 2;
    /**
     * Represents an unencrypted message with parsing error, an encrypted message with
     * authentication or decryption error, or any message with wrong message ID.
     */
    public static final int DECODE_STATUS_UNPROTECTED_ERROR = 3;

    /** This class represents common decoding result of an IKE message. */
    public abstract static class DecodeResult {
        public final int status;

        /** Construct an instance of DecodeResult. */
        protected DecodeResult(int status) {
            this.status = status;
        }
    }

    /** This class represents an IKE message has been successfully (decrypted and) decoded. */
    public static class DecodeResultOk extends DecodeResult {
        public final IkeMessage ikeMessage;
        public final byte[] firstPacket;

        public DecodeResultOk(IkeMessage ikeMessage, byte[] firstPacket) {
            super(DECODE_STATUS_OK);
            this.ikeMessage = ikeMessage;
            this.firstPacket = firstPacket;
        }
    }

    /**
     * This class represents IKE fragments are being reassembled to build a complete IKE message.
     *
     * <p>All IKE fragments should have the same IKE headers, except for the message length. This
     * class only stores the IKE header of the first arrived IKE fragment to represent the IKE
     * header of the complete IKE message. In this way we can verify all subsequent fragments'
     * headers against it.
     *
     * <p>The first payload type is only stored in the first fragment, as indicated in RFC 7383. So
     * this class only stores the next payload type field taken from the first fragment.
     */
    public static class DecodeResultPartial extends DecodeResult {
        public final int firstPayloadType;
        public final byte[] firstFragBytes;
        public final IkeHeader ikeHeader;
        public final byte[][] collectedFragsList;

        /**
         * Construct an instance of DecodeResultPartial with collected fragments and the newly
         * received fragment.
         *
         * <p>The newly received fragment has been validated against collected fragments during
         * decoding that all fragments have the same total fragments number and the newly received
         * fragment is not a replay.
         */
        public DecodeResultPartial(
                IkeHeader ikeHeader,
                byte[] inputPacket,
                IkeSkfPayload skfPayload,
                int nextPayloadType,
                @Nullable DecodeResultPartial collectedFragments) {
            super(DECODE_STATUS_PARTIAL);

            boolean isFirstFragment = 1 == skfPayload.fragmentNum;
            if (collectedFragments == null) {
                // First arrived IKE fragment
                this.ikeHeader = ikeHeader;
                this.firstPayloadType =
                        isFirstFragment ? nextPayloadType : IkePayload.PAYLOAD_TYPE_NO_NEXT;
                this.firstFragBytes = isFirstFragment ? inputPacket : null;
                this.collectedFragsList = new byte[skfPayload.totalFragments][];
            } else {
                this.ikeHeader = collectedFragments.ikeHeader;
                this.firstPayloadType =
                        isFirstFragment ? nextPayloadType : collectedFragments.firstPayloadType;
                this.firstFragBytes =
                        isFirstFragment ? inputPacket : collectedFragments.firstFragBytes;
                this.collectedFragsList = collectedFragments.collectedFragsList;
            }

            this.collectedFragsList[skfPayload.fragmentNum - 1] = skfPayload.getUnencryptedData();
        }

        /** Return if all IKE fragments have been collected */
        public boolean isAllFragmentsReceived() {
            for (byte[] frag : collectedFragsList) {
                if (frag == null) return false;
            }
            return true;
        }

        /** Reassemble all IKE fragments and return the unencrypted message body in byte array. */
        public byte[] reassembleAllFrags() {
            if (!isAllFragmentsReceived()) {
                throw new IllegalStateException("Not all fragments have been received");
            }

            int len = 0;
            for (byte[] frag : collectedFragsList) {
                len += frag.length;
            }

            ByteBuffer buffer = ByteBuffer.allocate(len);
            for (byte[] frag : collectedFragsList) {
                buffer.put(frag);
            }

            return buffer.array();
        }
    }

    /**
     * This class represents common information of error cases in decrypting and decoding message.
     */
    public abstract static class DecodeResultError extends DecodeResult {
        public final IkeException ikeException;

        protected DecodeResultError(int status, IkeException ikeException) {
            super(status);
            this.ikeException = ikeException;
        }
    }
    /**
     * This class represents that decoding errors have been found after the IKE message is
     * authenticated and decrypted.
     */
    public static class DecodeResultProtectedError extends DecodeResultError {
        public final byte[] firstPacket;

        public DecodeResultProtectedError(IkeException ikeException, byte[] firstPacket) {
            super(DECODE_STATUS_PROTECTED_ERROR, ikeException);
            this.firstPacket = firstPacket;
        }
    }
    /** This class represents errors have been found during message authentication or decryption. */
    public static class DecodeResultUnprotectedError extends DecodeResultError {
        public DecodeResultUnprotectedError(IkeException ikeException) {
            super(DECODE_STATUS_UNPROTECTED_ERROR, ikeException);
        }
    }

    /**
     * For setting mocked IIkeMessageHelper for testing
     *
     * @param helper the mocked IIkeMessageHelper
     */
    public static void setIkeMessageHelper(IIkeMessageHelper helper) {
        sIkeMessageHelper = helper;
    }
}