/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 *
 *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
 *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
 */

package sun.security.krb5.internal.rcache;

import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.StringTokenizer;

/**
 * The class represents an old style replay cache entry. It is only used in
 * a dfl file.
 *
 * @author Sun/Oracle
 * @author Yanni Zhang
 */
public class AuthTime {
    final int ctime;
    final int cusec;
    final String client;
    final String server;

    /**
     * Constructs an <code>AuthTime</code>.
     */
    public AuthTime(String client, String server,
            int ctime, int cusec) {
        this.ctime = ctime;
        this.cusec = cusec;
        this.client = client;
        this.server = server;
    }

    @Override
    public String toString() {
        return String.format("%d/%06d/----/%s", ctime, cusec, client);
    }

    // Methods used when saved in a dfl file. See DflCache.java

    /**
     * Reads an LC style string from a channel, which is a int32 length
     * plus a UTF-8 encoded string possibly ends with \0.
     * @throws IOException if there is a format error
     * @throws BufferUnderflowException if goes beyond the end
     */
    private static String readStringWithLength(SeekableByteChannel chan)
            throws IOException {
        ByteBuffer bb = ByteBuffer.allocate(4);
        bb.order(ByteOrder.nativeOrder());
        chan.read(bb);
        bb.flip();
        int len = bb.getInt();
        if (len > 1024) {
            // Memory attack? The string should be fairly short.
            throw new IOException("Invalid string length");
        }
        bb = ByteBuffer.allocate(len);
        if (chan.read(bb) != len) {
            throw new IOException("Not enough string");
        }
        byte[] data = bb.array();
        return (data[len-1] == 0)?
                new String(data, 0, len-1, StandardCharsets.UTF_8):
                new String(data, StandardCharsets.UTF_8);
    }

    /**
     * Reads an AuthTime or AuthTimeWithHash object from a channel.
     * @throws IOException if there is a format error
     * @throws BufferUnderflowException if goes beyond the end
     */
    public static AuthTime readFrom(SeekableByteChannel chan)
            throws IOException {
        String client = readStringWithLength(chan);
        String server = readStringWithLength(chan);
        ByteBuffer bb = ByteBuffer.allocate(8);
        chan.read(bb);
        bb.order(ByteOrder.nativeOrder());
        int cusec = bb.getInt(0);
        int ctime = bb.getInt(4);
        if (client.isEmpty()) {
            StringTokenizer st = new StringTokenizer(server, " :");
            if (st.countTokens() != 6) {
                throw new IOException("Incorrect rcache style");
            }
            st.nextToken();
            String hash = st.nextToken();
            st.nextToken();
            client = st.nextToken();
            st.nextToken();
            server = st.nextToken();
            return new AuthTimeWithHash(
                    client, server, ctime, cusec, hash);
        } else {
            return new AuthTime(
                    client, server, ctime, cusec);
        }
    }

    /**
     * Encodes to be used in a dfl file
     */
    protected byte[] encode0(String cstring, String sstring) {
        byte[] c = cstring.getBytes(StandardCharsets.UTF_8);;
        byte[] s = sstring.getBytes(StandardCharsets.UTF_8);;
        byte[] zero = new byte[1];
        int len = 4 + c.length + 1 + 4 + s.length + 1 + 4 + 4;
        ByteBuffer bb = ByteBuffer.allocate(len)
                .order(ByteOrder.nativeOrder());
        bb.putInt(c.length+1).put(c).put(zero)
                .putInt(s.length+1).put(s).put(zero)
                .putInt(cusec).putInt(ctime);
        return bb.array();
    }

    /**
     * Encodes to be used in a dfl file
     * @param withHash useless here
     */
    public byte[] encode(boolean withHash) {
        return encode0(client, server);
    }
}
