package org.freertr.pack;

import java.util.ArrayList;
import java.util.List;
import org.freertr.addr.addrIP;
import org.freertr.addr.addrIPv4;
import org.freertr.addr.addrIPv6;
import org.freertr.cry.cryEncrGeneric;
import org.freertr.cry.cryHashGeneric;
import org.freertr.cry.cryKeyGeneric;
import org.freertr.sec.secTransform;
import org.freertr.util.bits;
import org.freertr.util.debugger;
import org.freertr.util.logger;

/**
 * internet key exchange protocol (rfc5996) packet
 *
 * @author matecsaba
 */
public class packIke {

    /**
     * packet holder
     */
    public packHolder pckDat;

    /**
     * binary packet data
     */
    public byte[] pckBin;

    /**
     * size of packet
     */
    public int pckSiz;

    /**
     * initiator cookie
     */
    public long cookieI;

    /**
     * responder cookie
     */
    public long cookieR;

    /**
     * next header
     */
    public int pckNxt;

    /**
     * exchange type
     */
    public int xchgTyp;

    /**
     * flags
     */
    public int flags;

    /**
     * message id
     */
    public int msgId;

    /**
     * proposal number
     */
    public int proposNum;

    /**
     * protocol number
     */
    public int protoId;

    /**
     * initiator spi value
     */
    public int spiValI;

    /**
     * responder spi value
     */
    public int spiValR;

    /**
     * initiator spi old
     */
    public int spiOldI;

    /**
     * responder spi old
     */
    public int spiOldR;

    /**
     * original transform
     */
    public secTransform transorig;

    /**
     * selected transform
     */
    public secTransform transform;

    /**
     * true if this side is initiator, false for responder
     */
    public boolean initiator;

    /**
     * preshared key
     */
    public String preshared;

    /**
     * diffie hellman keys
     */
    public cryKeyGeneric keygen;

    /**
     * initiator nonce
     */
    public byte[] nonceI;

    /**
     * responder nonce
     */
    public byte[] nonceR;

    /**
     * diffie hellman common secret
     */
    public byte[] dhcomm;

    /**
     * skeyid
     */
    public byte[] skeyidG;

    /**
     * skeyid-d
     */
    public byte[] skeyidD;

    /**
     * skeyid-a
     */
    public byte[] skeyidA;

    /**
     * skeyid-e
     */
    public byte[] skeyidE;

    /**
     * skeyid-p
     */
    public byte[] skeyidP;

    /**
     * initiator's message
     */
    public byte[] msgI;

    /**
     * responder's message
     */
    public byte[] msgR;

    /**
     * initiator's identity
     */
    public byte[] idnI;

    /**
     * responder's identity
     */
    public byte[] idnR;

    /**
     * identification data
     */
    public addrIP ident;

    /**
     * port number
     */
    public final static int port = 500;

    /**
     * size of header
     */
    public final static int headSize = 28;

    /**
     * size of payload
     */
    public final static int paySize = 4;

    /**
     * none
     */
    public final static int payNone = 0;

    /**
     * security association
     */
    public final static int paySecAss = 33;

    /**
     * key exchange
     */
    public final static int payKeyEx = 34;

    /**
     * identification initiator
     */
    public final static int payIdntInit = 35;

    /**
     * identification responder
     */
    public final static int payIdntRspn = 36;

    /**
     * certificate data
     */
    public final static int payCrtDat = 37;

    /**
     * certificate request
     */
    public final static int payCrtReq = 38;

    /**
     * authentication
     */
    public final static int payAuthen = 39;

    /**
     * nonce
     */
    public final static int payNonce = 40;

    /**
     * notify
     */
    public final static int payNotify = 41;

    /**
     * delete
     */
    public final static int payDelete = 42;

    /**
     * vendor is
     */
    public final static int payVendor = 43;

    /**
     * traffic selector initiator
     */
    public final static int payTraffInit = 44;

    /**
     * traffic selector responder
     */
    public final static int payTraffResp = 45;

    /**
     * encrypted and authenticated
     */
    public final static int payEncrypt = 46;

    /**
     * configuration
     */
    public final static int payConfig = 47;

    /**
     * extensible authentication protocol
     */
    public final static int payExtAuth = 48;

    /**
     * ike sa init
     */
    public final static int xchgIkeSa = 34;

    /**
     * ike auth
     */
    public final static int xchgAuth = 35;

    /**
     * create child sa
     */
    public final static int xchgChild = 36;

    /**
     * informational
     */
    public final static int xchgInfo = 37;

    /**
     * initiator
     */
    public final static int flagInit = 0x08;

    /**
     * higher version
     */
    public final static int flagVers = 0x10;

    /**
     * response packet
     */
    public final static int flagResp = 0x20;

    /**
     * create new packet holder
     */
    public packIke() {
        pckDat = new packHolder(true, true);
    }

    /**
     * copy bytes of packet
     *
     * @return new instance
     */
    public packIke copyBytes() {
        packIke n = new packIke();
        n.cookieI = cookieI;
        n.cookieR = cookieR;
        n.transorig = transorig;
        n.transform = transform;
        n.initiator = initiator;
        n.preshared = preshared;
        n.keygen = keygen;
        n.nonceI = nonceI;
        n.nonceR = nonceR;
        n.skeyidA = skeyidA;
        n.skeyidD = skeyidD;
        n.skeyidE = skeyidE;
        n.skeyidG = skeyidG;
        n.skeyidP = skeyidP;
        n.msgI = msgI;
        n.msgR = msgR;
        n.idnI = idnI;
        n.idnR = idnR;
        n.msgId = msgId;
        n.spiValI = spiValI;
        n.spiValR = spiValR;
        n.spiOldI = spiOldI;
        n.spiOldR = spiOldR;
        return n;
    }

    /**
     * check if this is my peer
     *
     * @param c to check
     * @return true if yes, false if no
     */
    public boolean checkPeer(packIke c) {
        if (c == null) {
            return false;
        }
        if (c.cookieI != cookieI) {
            return false;
        }
        if (c.cookieR != cookieR) {
            return false;
        }
        return true;
    }

    /**
     * convert exchange type to string
     *
     * @param i exchange type
     * @return string
     */
    public static String exchange2string(int i) {
        switch (i) {
            case xchgIkeSa:
                return "ikeSa";
            case xchgAuth:
                return "ikeAuth";
            case xchgChild:
                return "childSa";
            case xchgInfo:
                return "inform";
            default:
                return "unknown=" + i;
        }
    }

    /**
     * convert payload to string
     *
     * @param i payload type
     * @return string
     */
    public static String payload2string(int i) {
        switch (i) {
            case payNone:
                return "none";
            case paySecAss:
                return "secAss";
            case payKeyEx:
                return "kex";
            case payIdntInit:
                return "idi";
            case payIdntRspn:
                return "idr";
            case payCrtDat:
                return "cert";
            case payCrtReq:
                return "certReq";
            case payAuthen:
                return "auth";
            case payNonce:
                return "nonce";
            case payNotify:
                return "notify";
            case payDelete:
                return "delete";
            case payVendor:
                return "vendor";
            case payTraffInit:
                return "tsi";
            case payTraffResp:
                return "tsr";
            case payEncrypt:
                return "crypt";
            case payConfig:
                return "config";
            case payExtAuth:
                return "eap";
            default:
                return "unknown=" + i;
        }
    }

    /**
     * parse one packet
     *
     * @return false on success, true on error
     */
    public boolean headerParse() {
        pckBin = pckDat.getCopy();
        if (pckDat.dataSize() < headSize) {
            return true;
        }
        cookieI = pckDat.msbGetQ(0);
        cookieR = pckDat.msbGetQ(8);
        pckNxt = pckDat.getByte(16);
        if (pckDat.getByte(17) != 0x20) {
            return true;
        }
        xchgTyp = pckDat.getByte(18);
        flags = pckDat.getByte(19);
        msgId = pckDat.msbGetD(20);
        pckSiz = pckDat.msbGetD(24) - headSize;
        pckDat.getSkip(headSize);
        if (pckSiz < 0) {
            return true;
        }
        if (pckSiz > pckDat.dataSize()) {
            return true;
        }
        pckDat.setDataSize(pckSiz);
        headerDump("rx");
        return false;
    }

    /**
     * create header
     */
    public void headerCreate() {
        pckDat.msbPutQ(0, cookieI);
        pckDat.msbPutQ(8, cookieR);
        pckDat.putByte(16, pckNxt);
        pckDat.putByte(17, 0x20); // version
        pckDat.putByte(18, xchgTyp);
        pckDat.putByte(19, flags);
        pckDat.msbPutD(20, msgId);
        pckDat.msbPutD(24, pckDat.dataSize() + headSize);
        pckDat.putSkip(headSize);
        pckDat.merge2beg();
        pckBin = pckDat.getCopy();
        headerDump("tx");
    }

    private void headerDump(String dir) {
        if (!debugger.secIkeTraf) {
            return;
        }
        logger.debug(dir + " " + exchange2string(xchgTyp) + " cookie=" + cookieI + "/" + cookieR + " msgid=" + msgId);
    }

    /**
     * find one payload
     *
     * @param needed type of payload
     * @return size of payload, negative if not found
     */
    protected int headerFind(int needed) {
        pckDat.setBytesLeft(pckSiz);
        int ths = pckNxt;
        for (;;) {
            if (pckDat.dataSize() < paySize) {
                return -1;
            }
            int nxt = pckDat.getByte(0);
            int siz = pckDat.msbGetW(2) - paySize;
            pckDat.getSkip(paySize);
            if (siz < 0) {
                return -1;
            }
            if (siz > pckDat.dataSize()) {
                return -1;
            }
            if (ths == needed) {
                return siz;
            }
            pckDat.getSkip(siz);
            ths = nxt;
        }
    }

    /**
     * write one payload
     *
     * @param typ type of payload
     */
    protected void headerWrite(int typ) {
        int siz = pckDat.headSize();
        pckDat.merge2beg();
        pckDat.putByte(0, pckNxt);
        pckDat.putByte(1, 0);
        pckDat.msbPutW(2, siz + paySize);
        pckDat.putSkip(paySize);
        pckDat.merge2beg();
        pckNxt = typ;
    }

    /**
     * read up header
     *
     * @param needed type of payload
     * @return bytes in header, null if not found
     */
    protected byte[] headerRead(int needed) {
        int i = headerFind(needed);
        if (i < 0) {
            return null;
        }
        byte[] buf = new byte[i];
        pckDat.getCopy(buf, 0, 0, i);
        return buf;
    }

    /**
     * read current header
     *
     * @return bytes
     */
    public byte[] headerCurr() {
        int siz = pckDat.dataSize();
        byte[] buf = new byte[siz - paySize];
        pckDat.getCopy(buf, 0, paySize, buf.length);
        return buf;
    }

    /**
     * check if empty
     *
     * @return true if yes, false if no
     */
    public boolean isEmpty() {
        return pckNxt == 0;
    }

    /**
     * check if reply
     *
     * @return true if yes, false if no
     */
    public boolean isReply() {
        return (flags & flagResp) != 0;
    }

    /**
     * set flags
     *
     * @param reply reply
     */
    public void setFlags(boolean reply) {
        flags = 0;
        if (initiator) {
            flags = flagInit;
        } else {
            flags = 0;
        }
        if (reply) {
            flags |= flagResp;
        }
    }

    /**
     * fill up security association
     *
     * @param ike true for ike sa, false of ipsec sa
     */
    public void secAssFill(boolean ike) {
        transform = transorig.copyBytes();
        proposNum = 1;
        if (ike) {
            protoId = 1;
        } else {
            transform.prfAlg = 0;
            transform.groupNum = 0;
            transform.encapMet = 1;
            protoId = 3;
        }
    }

    /**
     * parse security association header
     *
     * @param ike true for ike sa, false of ipsec sa
     * @return false on success, true on error
     */
    public boolean secAssParse(boolean ike) {
        if (headerFind(paySecAss) < 0) {
            return true;
        }
        pckDat.getSkip(paySize);
        proposNum = pckDat.getByte(0);
        protoId = pckDat.getByte(1);
        int spiSize = pckDat.getByte(2);
        int transNum = pckDat.getByte(3);
        pckDat.getSkip(4);
        if (spiSize == 4) {
            spiValI = pckDat.msbGetD(0);
        }
        pckDat.getSkip(spiSize);
        List<secTransform> trans = new ArrayList<secTransform>();
        for (int i = 0; i < transNum; i++) {
            secTransform t = new secTransform();
            if (t.parse2transform(pckDat)) {
                continue;
            }
            trans.add(t);
        }
        secTransform t;
        if (ike) {
            t = new secTransform();
            t.groupNum = transform.groupNum;
            if (secTransform.findMatching(trans, t) < 0) {
                return true;
            }
            if (transform.prfAlg != 0) {
                t = new secTransform();
                t.prfAlg = transform.prfAlg;
                if (secTransform.findMatching(trans, t) < 0) {
                    return true;
                }
            }
        }
        t = new secTransform();
        t.encrAlg = transform.encrAlg;
        t.encrKey = transform.encrKey;
        if (secTransform.findMatching(trans, t) < 0) {
            return true;
        }
        if (transform.hashAlg != 0) {
            t = new secTransform();
            t.hashAlg = transform.hashAlg;
            if (secTransform.findMatching(trans, t) < 0) {
                return true;
            }
        }
        secAssDump("rx");
        return false;
    }

    /**
     * create security association header
     */
    public void secAssCreate() {
        int p = transform.create2transform(pckDat);
        int i = pckDat.headSize();
        int o = 0;
        if (spiValI != 0) {
            o = 4;
        }
        pckDat.merge2beg();
        pckDat.msbPutW(0, 0); // next payload
        pckDat.msbPutW(2, 8 + i + o); // size
        pckDat.putByte(4, proposNum);
        pckDat.putByte(5, protoId);
        pckDat.putByte(6, o); // spi size
        pckDat.putByte(7, p); // number of transforms
        pckDat.putSkip(8);
        if (spiValI != 0) {
            pckDat.msbPutD(0, spiValI);
            pckDat.putSkip(4);
        }
        pckDat.unMergeBytes(i);
        headerWrite(paySecAss);
        secAssDump("tx");
    }

    private void secAssDump(String dir) {
        if (!debugger.secIkeTraf) {
            return;
        }
        logger.debug(dir + " prop=" + proposNum + " prot=" + protoId + " spi=" + spiValI + " trans=" + transform);
    }

    /**
     * fill key exchange header
     */
    public void keyXchgFill() {
        if (keygen == null) {
            keygen = transform.getGroup();
        }
        if (initiator) {
            keygen.keyClntInit();
        } else {
            keygen.keyServInit();
        }
        xchgTyp = xchgIkeSa;
    }

    /**
     * parse key exchange header
     *
     * @return false on success, true on error
     */
    public boolean keyXchgParse() {
        byte[] buf = headerRead(payKeyEx);
        if (buf == null) {
            return true;
        }

        if (keygen == null) {
            keygen = transform.getGroup();
        }
        if (bits.msbGetW(buf, 0) != transform.groupNum) {
            return true;
        }
        if (initiator) {
            keygen.keyServIke(buf, 4);
        } else {
            keygen.keyClntIke(buf, 4);
        }
        keyXchgDump("rx");
        return false;
    }

    /**
     * create key exchange header
     */
    public void keyXchgCreate() {
        pckDat.msbPutW(0, transform.groupNum);
        pckDat.msbPutW(2, 0);
        pckDat.putSkip(4);
        byte[] buf;
        if (initiator) {
            buf = keygen.keyClntIke();
        } else {
            buf = keygen.keyServIke();
        }
        pckDat.putCopy(buf, 0, 0, buf.length);
        pckDat.putSkip(buf.length);
        headerWrite(payKeyEx);
        keyXchgDump("tx");
    }

    private void keyXchgDump(String dir) {
        if (!debugger.secIkeTraf) {
            return;
        }
        logger.debug(dir + " kex " + keygen.keyDump());
    }

    /**
     * generate one nonce
     *
     * @return nonce data
     */
    public static byte[] nonceFill() {
        byte[] buf = new byte[20];
        for (int i = 0; i < buf.length; i++) {
            buf[i] = (byte) bits.randomB();
        }
        return buf;
    }

    /**
     * parse nonce header
     *
     * @return false on success, true on error
     */
    public boolean nonceParse() {
        nonceI = headerRead(payNonce);
        if (nonceI == null) {
            return true;
        }
        nonceDump("rx");
        return false;
    }

    /**
     * create nonce header
     */
    public void nonceCreate() {
        pckDat.putCopy(nonceI, 0, 0, nonceI.length);
        pckDat.putSkip(nonceI.length);
        headerWrite(payNonce);
        nonceDump("tx");
    }

    private void nonceDump(String dir) {
        if (!debugger.secIkeTraf) {
            return;
        }
        logger.debug(dir + " nonce=" + bits.byteDump(nonceI, 0, -1));
    }

    private byte[] doPrng(byte[] k, byte[] s, int len) {
        byte[] res = new byte[0];
        int cnt = 0;
        byte[] prev = new byte[0];
        for (;;) {
            cnt++;
            if (res.length > len) {
                break;
            }
            cryHashGeneric h = transorig.getHprf(k);
            h.update(prev);
            h.update(s);
            prev = new byte[1];
            prev[0] = (byte) (cnt & 0xff);
            h.update(prev);
            prev = h.finish();
            res = bits.byteConcat(res, prev);
        }
        return res;
    }

    /**
     * compute keys
     */
    public void computeKeys() {
        if (initiator) {
            keygen.keyClntCalc();
        } else {
            keygen.keyServCalc();
        }
        dhcomm = keygen.keyCommonIke();
        cryHashGeneric h = transorig.getHprf(bits.byteConcat(nonceI, nonceR));
        h.update(dhcomm);
        skeyidG = h.finish();
        byte[] buf = new byte[16];
        bits.msbPutQ(buf, 0, cookieI);
        bits.msbPutQ(buf, 8, cookieR);
        buf = bits.byteConcat(bits.byteConcat(nonceI, nonceR), buf);
        buf = doPrng(skeyidG, buf, 1024);
        int p = 0;
        skeyidD = new byte[skeyidG.length];
        bits.byteCopy(buf, p, skeyidD, 0, skeyidD.length);
        p += skeyidD.length;
        if (!transform.isAead()) {
            skeyidA = new byte[skeyidG.length * 2];
            bits.byteCopy(buf, p, skeyidA, 0, skeyidA.length);
            p += skeyidA.length;
        }
        int i = transform.getKeyS();
        if (transform.isAead()) {
            i += 4;
        }
        skeyidE = new byte[i * 2];
        bits.byteCopy(buf, p, skeyidE, 0, skeyidE.length);
        p += skeyidE.length;
        skeyidP = new byte[skeyidG.length * 2];
        bits.byteCopy(buf, p, skeyidP, 0, skeyidP.length);
        p += skeyidP.length;
        if (debugger.secIkeTraf) {
            logger.debug("dh=" + bits.byteDump(dhcomm, 0, -1) + " skeyG=" + bits.byteDump(skeyidG, 0, -1) + " skeyD=" + bits.byteDump(skeyidD, 0, -1) + " skeyA=" + bits.byteDump(skeyidA, 0, -1) + " skeyE=" + bits.byteDump(skeyidE, 0, -1) + " skeyP=" + bits.byteDump(skeyidP, 0, -1));
        }
    }

    private byte[] getPart(byte[] buf, boolean encrtypt, boolean forced) {
        byte[] res = new byte[buf.length / 2];
        if (forced) {
            forced = encrtypt;
        } else {
            forced = initiator ^ encrtypt;
        }
        if (forced) {
            bits.byteCopy(buf, res.length, res, 0, res.length);
        } else {
            bits.byteCopy(buf, 0, res, 0, res.length);
        }
        return res;
    }

    /**
     * parse encrypted header
     *
     * @return false on success, true on error
     */
    public boolean encryptParse() {
        byte[] b1 = headerRead(payEncrypt);
        if (b1 == null) {
            return true;
        }
        pckNxt = pckDat.getByte(-4);
        if (transform.isAead()) {
            int o = pckDat.dataSize();
            byte[] b2 = new byte[8];
            pckDat.getCopy(b2, 0, 0, b2.length);
            pckDat.getSkip(b2.length);
            b1 = pckDat.getCopy();
            pckDat.setBytesLeft(pckSiz + headSize);
            byte[] b3 = new byte[pckDat.dataSize() - o];
            pckDat.getCopy(b3, 0, 0, b3.length);
            byte[] b4 = getPart(skeyidE, false, false);
            byte[] b5 = new byte[4];
            bits.byteCopy(b4, b4.length - b5.length, b5, 0, b5.length);
            b2 = bits.byteConcat(b5, b2);
            b5 = new byte[b4.length - 4];
            bits.byteCopy(b4, 0, b5, 0, b5.length);
            cryEncrGeneric e = transform.getEncr();
            e.init(b5, b2, false);
            e.authAdd(b3);
            pckSiz = e.update(b1, 0, b1.length);
            if (pckSiz < 0) {
                return true;
            }
            pckSiz--;
            pckSiz -= b1[pckSiz];
            pckDat.setDataSize(0);
            pckDat.putCopy(b1, 0, 0, pckSiz);
            pckDat.putSkip(pckSiz);
            pckDat.merge2end();
            encryptDump("rx");
            return false;
        }
        int o = transform.getHashS();
        if (b1.length < o) {
            return true;
        }
        pckDat.setBytesLeft(pckSiz + headSize);
        byte[] b2 = new byte[pckDat.dataSize() - o];
        pckDat.getCopy(b2, 0, 0, b2.length);
        cryHashGeneric h = transform.getHmac(getPart(skeyidA, false, false));
        h.update(b2);
        pckDat.setBytesLeft(o);
        b2 = h.finish();
        if (bits.byteComp(b2, 0, pckDat.getCopy(), 0, o) != 0) {
            return true;
        }
        pckSiz = b1.length - o;
        cryEncrGeneric e = transform.getEncr();
        e.init(getPart(skeyidE, false, false), new byte[e.getBlockSize()], false);
        e.update(b1, 0, pckSiz);
        pckSiz--;
        pckSiz -= b1[pckSiz];
        o = e.getBlockSize();
        pckSiz -= o;
        pckDat.setDataSize(0);
        pckDat.putCopy(b1, o, 0, pckSiz);
        pckDat.putSkip(pckSiz);
        pckDat.merge2end();
        encryptDump("rx");
        return false;
    }

    /**
     * create encrypted header
     */
    public void encryptCreate() {
        encryptDump("tx");
        cryEncrGeneric e = transform.getEncr();
        if (transform.isAead()) {
            pckDat.putByte(0, 0);
            pckDat.putSkip(1);
            pckDat.merge2end();
            byte[] b1 = pckDat.getCopy();
            pckDat.getSkip(b1.length);
            byte[] b2 = new byte[8];
            for (int i = 0; i < b2.length; i++) {
                b2[i] = (byte) bits.randomB();
            }
            byte[] b4 = getPart(skeyidE, true, false);
            byte[] b3 = new byte[4];
            bits.byteCopy(b4, b4.length - b3.length, b3, 0, b3.length);
            b2 = bits.byteConcat(b3, b2);
            b3 = new byte[b4.length - 4];
            bits.byteCopy(b4, 0, b3, 0, b3.length);
            int o = e.getTagSize();
            pckDat.putSkip(b2.length - 4);
            pckDat.putSkip(b1.length);
            pckDat.putSkip(o);
            int i = pckDat.dataSize() + pckDat.headSize();
            headerWrite(payEncrypt);
            headerCreate();
            pckDat.setDataSize(pckDat.dataSize() - i);
            b4 = pckDat.getCopy();
            pckDat.getSkip(headSize);
            e.init(b3, b2, true);
            e.authAdd(b4);
            b1 = bits.byteConcat(b1, new byte[o]);
            e.update(b1, 0, b1.length - o);
            pckDat.putCopy(b2, 4, 0, b2.length - 4);
            pckDat.putSkip(b2.length - 4);
            pckDat.putCopy(b1, 0, 0, b1.length);
            pckDat.putSkip(b1.length);
            pckDat.merge2end();
            return;
        }
        int o = e.getBlockSize();
        e.init(getPart(skeyidE, true, false), new byte[e.getBlockSize()], true);
        for (int i = 0; i < o; i++) {
            pckDat.putByte(i, bits.randomB());
        }
        pckDat.putSkip(o);
        pckDat.merge2beg();
        int i = o - ((pckDat.dataSize() + 1) % o);
        pckDat.putFill(0, i, 0);
        pckDat.putByte(i, i);
        pckDat.putSkip(i + 1);
        pckDat.merge2end();
        byte[] b1 = pckDat.getCopy();
        e.update(b1, 0, b1.length);
        pckDat.setDataSize(0);
        pckDat.putCopy(b1, 0, 0, b1.length);
        pckDat.putSkip(b1.length);
        o = transform.getHashS();
        pckDat.putSkip(o);
        headerWrite(payEncrypt);
        headerCreate();
        pckDat.setDataSize(pckDat.dataSize() - o);
        cryHashGeneric h = transform.getHmac(getPart(skeyidA, true, false));
        h.update(pckDat.getCopy());
        b1 = h.finish();
        pckDat.getSkip(headSize);
        pckDat.putCopy(b1, 0, 0, o);
        pckDat.putSkip(o);
        pckDat.merge2end();
    }

    private void encryptDump(String dir) {
        if (!debugger.secIkeTraf) {
            return;
        }
        logger.debug(dir + " encrypt=(" + pckNxt + ") " + pckDat.dump());
    }

    /**
     * fill id header
     *
     * @param adr address to add
     */
    public void identFill(addrIP adr) {
        ident = adr.copyBytes();
    }

    /**
     * create id header
     */
    public void identCreate() {
        pckDat.msbPutD(0, 0);
        if (ident.isIPv4()) {
            pckDat.putByte(0, 1);
            pckDat.putAddr(4, ident.toIPv4());
            pckDat.putSkip(addrIPv4.size);
        } else {
            pckDat.putByte(0, 5);
            pckDat.putAddr(4, ident.toIPv6());
            pckDat.putSkip(addrIPv6.size);
        }
        pckDat.putSkip(4);
        if (initiator) {
            headerWrite(payIdntInit);
        } else {
            headerWrite(payIdntRspn);
        }
        identDump("tx");
    }

    /**
     * parse id header
     *
     * @return false on success, true on error
     */
    public boolean identParse() {
        if (initiator) {
            int i = headerFind(payIdntRspn);
            if (i < 0) {
                return true;
            }
            idnR = new byte[i];
            pckDat.getCopy(idnR, 0, 0, idnR.length);
        } else {
            int i = headerFind(payIdntInit);
            if (i < 0) {
                return true;
            }
            idnI = new byte[i];
            pckDat.getCopy(idnI, 0, 0, idnI.length);
        }
        ident = new addrIP();
        switch (pckDat.getByte(0)) {
            case 1:
                addrIPv4 adr4 = new addrIPv4();
                pckDat.getAddr(adr4, 4);
                ident.fromIPv4addr(adr4);
                break;
            case 5:
                addrIPv6 adr6 = new addrIPv6();
                pckDat.getAddr(adr6, 4);
                ident.fromIPv6addr(adr6);
                break;
            default:
                return true;
        }
        identDump("rx");
        return false;
    }

    private void identDump(String dir) {
        if (!debugger.secIkeTraf) {
            return;
        }
        logger.debug(dir + " ident=" + ident);
    }

    private int proto2trafsel(boolean ipv6) {
        if (ipv6) {
            return 8;
        } else {
            return 7;
        }
    }

    /**
     * parse traffic selector header
     *
     * @param init initiator side
     * @param ipv6 protected protocol
     * @return false on success, true on error
     */
    public boolean trafselParse(boolean init, boolean ipv6) {
        if (init) {
            if (headerFind(payTraffInit) < 0) {
                return true;
            }
        } else {
            if (headerFind(payTraffResp) < 0) {
                return true;
            }
        }
        if (pckDat.getByte(0) < 1) {
            return true;
        }
        pckDat.getSkip(4);
        if (pckDat.getByte(0) != proto2trafsel(ipv6)) {
            return true;
        }
        if (pckDat.getByte(1) != 0) {
            return true;
        }
        pckDat.getSkip(4);
        if (pckDat.msbGetW(0) != 0) {
            return true;
        }
        if (pckDat.msbGetW(2) != 0xffff) {
            return true;
        }
        pckDat.getSkip(4);
        ident = new addrIP();
        if (ipv6) {
            addrIPv6 adr6 = new addrIPv6();
            pckDat.getAddr(adr6, 0);
            pckDat.getAddr(adr6, addrIPv6.size);
            pckDat.getSkip(addrIPv6.size * 2);
            ident.fromIPv6addr(adr6);
        } else {
            addrIPv4 adr4 = new addrIPv4();
            pckDat.getAddr(adr4, 0);
            pckDat.getAddr(adr4, addrIPv4.size);
            pckDat.getSkip(addrIPv4.size * 2);
            ident.fromIPv4addr(adr4);
        }
        trafselDump("rx");
        return false;
    }

    /**
     * create traffic selector header
     *
     * @param init initiator side
     * @param ipv6 protected protocol
     */
    public void trafselCreate(boolean init, boolean ipv6) {
        pckDat.msbPutD(0, 0);
        pckDat.putByte(0, 1);
        pckDat.putByte(4, proto2trafsel(ipv6));
        pckDat.putByte(5, 0);
        pckDat.msbPutW(6, 0);
        pckDat.msbPutW(8, 0);
        pckDat.msbPutW(10, 0xffff);
        int p = 12;
        ident = new addrIP();
        if (ipv6) {
            addrIPv6 adr6 = new addrIPv6();
            pckDat.putAddr(p, adr6);
            p += addrIPv6.size;
            adr6.setNot(adr6);
            pckDat.putAddr(p, adr6);
            p += addrIPv6.size;
            ident.fromIPv6addr(adr6);
        } else {
            addrIPv4 adr4 = new addrIPv4();
            pckDat.putAddr(p, adr4);
            p += addrIPv4.size;
            adr4.setNot(adr4);
            pckDat.putAddr(p, adr4);
            p += addrIPv4.size;
            ident.fromIPv4addr(adr4);
        }
        pckDat.msbPutW(6, p - 4);
        pckDat.putSkip(p);
        if (init) {
            headerWrite(payTraffInit);
        } else {
            headerWrite(payTraffResp);
        }
        xchgTyp = xchgChild;
        trafselDump("tx");
    }

    private void trafselDump(String dir) {
        if (!debugger.secIkeTraf) {
            return;
        }
        logger.debug(dir + " trafsel=" + ident);
    }

    /**
     * parse authentication header
     *
     * @return false on success, true on error
     */
    public boolean authenParse() {
        byte[] b1 = headerRead(payAuthen);
        if (b1 == null) {
            return true;
        }
        if (b1[0] != 2) {
            return true;
        }
        byte[] b2 = getAuthen(!initiator);
        if ((b1.length - 4) != b2.length) {
            return true;
        }
        return bits.byteComp(b1, 4, b2, 0, b2.length) != 0;
    }

    /**
     * create authentication header
     */
    public void authenCreate() {
        pckDat.msbPutD(0, 0);
        pckDat.putByte(0, 2);
        pckDat.putSkip(4);
        byte[] buf = getAuthen(initiator);
        pckDat.putCopy(buf, 0, 0, buf.length);
        pckDat.putSkip(buf.length);
        headerWrite(payAuthen);
        xchgTyp = xchgAuth;
    }

    private byte[] getAuthen(boolean init) {
        byte[] msg;
        byte[] non;
        byte[] idn;
        byte[] skp;
        if (init) {
            msg = msgI;
            non = nonceR;
            idn = idnI;
            skp = getPart(skeyidP, false, true);
        } else {
            msg = msgR;
            non = nonceI;
            idn = idnR;
            skp = getPart(skeyidP, true, true);
        }
        cryHashGeneric h = transorig.getHprf(skp);
        h.update(idn);
        skp = h.finish();
        h = transorig.getHprf(preshared.getBytes());
        h.update("Key Pad for IKEv2".getBytes());
        h = transorig.getHprf(h.finish());
        h.update(msg);
        h.update(non);
        h.update(skp);
        return h.finish();
    }

    /**
     * parse delete header
     *
     * @return false on success, true on error
     */
    public boolean deleteParse() {
        if (headerFind(payDelete) < 0) {
            return true;
        }
        if (pckDat.getByte(0) != 3) {
            return true;
        }
        if (pckDat.getByte(1) != 4) {
            return true;
        }
        if (pckDat.msbGetW(2) < 1) {
            return true;
        }
        spiValI = pckDat.msbGetD(4);
        deleteDump("rx");
        return false;
    }

    /**
     * create delete header
     */
    public void deleteCreate() {
        pckDat.putByte(0, 3);
        pckDat.putByte(1, 4);
        pckDat.msbPutW(2, 1);
        pckDat.msbPutD(4, spiValI);
        pckDat.putSkip(8);
        headerWrite(payDelete);
        xchgTyp = xchgInfo;
        deleteDump("tx");
    }

    private void deleteDump(String dir) {
        if (!debugger.secIkeTraf) {
            return;
        }
        logger.debug(dir + " delete spi=" + spiValI);
    }

    /**
     * update esp handler
     *
     * @param esp esp handler to update
     * @param spi spi value
     * @param encr true for encryption, false for decryption
     * @param init initiator side
     */
    public void updateEsp(packEsp esp, int spi, boolean encr, boolean init) {
        byte[] buf = doPrng(skeyidD, bits.byteConcat(nonceI, nonceR), 256);
        if (debugger.secIkeTraf) {
            logger.debug("spi=" + spi + " key=" + bits.byteDump(buf, 0, -1));
        }
        esp.spi = spi;
        cryEncrGeneric ciph = transform.getEncr();
        esp.hasher = transform.getHash();
        esp.encrSize = ciph.getBlockSize();
        esp.tagSize = ciph.getTagSize();
        int i = transform.getKeyS();
        int p;
        int o;
        if (transform.isAead()) {
            o = 4;
        } else {
            o = esp.hasher.getHashSize();
            esp.hashSize = transform.getHashS();
        }
        if (init) {
            p = i + o;
        } else {
            p = 0;
        }
        byte[] last = new byte[i];
        bits.byteCopy(buf, p, last, 0, last.length);
        ciph.init(last, new byte[ciph.getIVsize()], encr);
        esp.keyEncr = last;
        last = new byte[o];
        bits.byteCopy(buf, p + i, last, 0, last.length);
        if (!transform.isAead()) {
            esp.hasher = transform.getHmac(last);
        }
        esp.cipher = ciph;
        esp.keyHash = last;
        esp.doInit();
    }

}
