/* WolfSSLX509Name.java
 *
 * Copyright (C) 2006-2025 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */
package com.wolfssl;

/**
 * WolfSSLX509Name class, wraps native WOLFSSL_X509_NAME functionality.
 */
public class WolfSSLX509Name {

    private boolean active = false;
    private long x509NamePtr = 0;

    /* Lock around active state */
    private final Object stateLock = new Object();

    /* Lock around x509NamePtr pointer access */
    private final Object x509NameLock = new Object();

    /* Cache name elements in Java before pushing through JNI, for easier
     * retrieval from getXXX() methods */
    private String countryName               = null;
    private String stateOrProvinceName       = null;
    private String streetAddress             = null;
    private String localityName              = null;
    private String surname                   = null;
    private String commonName                = null;
    private String emailAddress              = null;
    private String organizationName          = null;
    private String organizationalUnitName    = null;
    private String postalCode                = null;
    private String userId                    = null;

    /* Encoding types, matched to native define values */
    private static final int MBSTRING_UTF8 = 0x100;

    /* Native JNI methods */
    static native long X509_NAME_new();
    static native void X509_NAME_free(long x509Name);
    static native int X509_NAME_add_entry_by_txt(long x509Name, String field,
        int type, byte[] entry, int len, int loc, int set);

    /**
     * Create new empty WolfSSLX509Name object.
     *
     * @throws WolfSSLException if native API call fails.
     */
    public WolfSSLX509Name() throws WolfSSLException {

        x509NamePtr = X509_NAME_new();
        if (x509NamePtr == 0) {
            throw new WolfSSLException("Failed to create WolfSSLX509Name");
        }

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, x509NamePtr,
            () -> "creating new WolfSSLX509Name");

        synchronized (stateLock) {
            this.active = true;
        }
    }

    /**
     * Verifies that the current WolfSSLX509Name object is active.
     *
     * @throws IllegalStateException if object has been freed
     */
    private void confirmObjectIsActive()
        throws IllegalStateException {

        synchronized (stateLock) {
            if (this.active == false) {
                throw new IllegalStateException(
                    "WolfSSLX509Name object has been freed");
            }
        }
    }

    /**
     * For package use only, return native WOLFSSL_X509_NAME pointer.
     *
     * @return native WOLFSSL_X509_POINTER value
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    protected long getNativeX509NamePtr() throws IllegalStateException {

        confirmObjectIsActive();

        /* TODO lock around x509NamePtr for caller use */
        synchronized (x509NameLock) {
            return this.x509NamePtr;
        }
    }

    /**
     * Private helper function to call native JNI function
     * X509_NAME_add_entry_by_txt().
     *
     * @param field String containing field name to set, for example
     *              "countryName"
     * @param entry String value to store into field
     *
     * @throws WolfSSLException if arguments are invalid or error occurs
     *         with native JNI call.
     */
    private synchronized void addEntryByTxt(String field, String entry)
        throws WolfSSLException {

        int ret = 0;

        if (field == null || entry == null) {
            throw new WolfSSLException(
                "field or entry is null in addEntryByTxt()");
        }

        synchronized (x509NameLock) {
            ret = X509_NAME_add_entry_by_txt(this.x509NamePtr, field,
                    MBSTRING_UTF8, entry.getBytes(),
                    entry.getBytes().length, -1, 0);
        }

        if (ret != WolfSSL.SSL_SUCCESS) {
            throw new WolfSSLException("Error setting " + field +
                " into WolfSSLX509Name (error: " + ret + ")");
        }
    }

    /**
     * Set country name for this name object.
     *
     * @param countryName String containing country name to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setCountryName(String countryName)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setCountryName(" + countryName + ")");

        addEntryByTxt("countryName", countryName);
        this.countryName = countryName;
    }

    /**
     * Set state or province name for this name object.
     *
     * @param name String containing state or province name to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setStateOrProvinceName(String name)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setStateOrProvinceName(" + name + ")");

        addEntryByTxt("stateOrProvinceName", name);
        this.stateOrProvinceName = name;
    }

    /**
     * Set street address for this name object.
     *
     * @param address String containing street address to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setStreetAddress(String address)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setStreetAddress(" + address + ")");

        addEntryByTxt("streetAddress", address);
        this.streetAddress = address;
    }

    /**
     * Set locality name / city for this name object.
     *
     * @param name String containing locality name to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setLocalityName(String name)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setLocalityName(" + name + ")");

        addEntryByTxt("localityName", name);
        this.localityName = name;
    }

    /**
     * Set surname for this name object.
     *
     * @param name String containing surname to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setSurname(String name)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setSurname(" + name + ")");

        addEntryByTxt("surname", name);
        this.surname = name;
    }

    /**
     * Set common name for this name object.
     *
     * @param name String containing common name to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setCommonName(String name)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setCommonName(" + name + ")");

        addEntryByTxt("commonName", name);
        this.commonName = name;
    }

    /**
     * Set email address for this name object.
     *
     * @param email String containing email address to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setEmailAddress(String email)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setEmailAddress(" + email + ")");

        addEntryByTxt("emailAddress", email);
        this.emailAddress = email;
    }

    /**
     * Set organization name for this name object.
     *
     * @param name String containing organization name to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setOrganizationName(String name)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setOrganizationName(" + name + ")");

        addEntryByTxt("organizationName", name);
        this.organizationName = name;
    }

    /**
     * Set organizational unit name for this name object.
     *
     * @param name String containing organizational unit name to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setOrganizationalUnitName(String name)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setOrganizationalUnitName(" + name + ")");

        addEntryByTxt("organizationalUnitName", name);
        this.organizationalUnitName = name;
    }

    /**
     * Set postal code for this name object.
     *
     * @param code String containing postal code to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setPostalCode(String code)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setPostalCode(" + code + ")");

        addEntryByTxt("postalCode", code);
        this.postalCode = code;
    }

    /**
     * Set user ID for this name object.
     *
     * @param id String containing user ID to be set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     * @throws WolfSSLException if native JNI error has occurred, or input
     *         argument is invalid.
     */
    public synchronized void setUserId(String id)
        throws IllegalStateException, WolfSSLException {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered setUserId(" + id + ")");

        addEntryByTxt("userId", id);
        this.userId = id;
    }

    /**
     * Get country name set in this object.
     *
     * @return country name string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getCountryName() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getCountryName()");

        return this.countryName;
    }

    /**
     * Get state or province name set in this object.
     *
     * @return state or province name string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getStateOrProvinceName() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getStateOrProvinceName()");

        return this.stateOrProvinceName;
    }

    /**
     * Get street address set in this object.
     *
     * @return street address string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getStreetAddress() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getStreetAddress()");

        return this.streetAddress;
    }

    /**
     * Get locality name set in this object.
     *
     * @return locality name string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getLocalityName() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getLocalityName()");

        return this.localityName;
    }

    /**
     * Get surname set in this object.
     *
     * @return surname string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getSurname() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getSurname()");

        return this.surname;
    }

    /**
     * Get common name set in this object.
     *
     * @return common name string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getCommonName() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getCommonName()");

        return this.commonName;
    }

    /**
     * Get email address set in this object.
     *
     * @return email address string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getEmailAddress() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getEmailAddress()");

        return this.emailAddress;
    }

    /**
     * Get organization name set in this object.
     *
     * @return organization name string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getOrganizationName() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getOrganizationName()");

        return this.organizationName;
    }

    /**
     * Get organizational unit name set in this object.
     *
     * @return organizational unit name string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getOrganizationalUnitName() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getOrganizationalUnitName()");

        return this.organizationalUnitName;
    }

    /**
     * Get postal code set in this object.
     *
     * @return postal code string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getPostalCode() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getPostalCode()");

        return this.postalCode;
    }

    /**
     * Get user ID set in this object.
     *
     * @return user ID string, or null if not yet set
     *
     * @throws IllegalStateException if WolfSSLX509Name has been freed.
     */
    public synchronized String getUserId() {

        confirmObjectIsActive();

        WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
            WolfSSLDebug.INFO, this.x509NamePtr,
            () -> "entered getUserId()");

        return this.userId;
    }

    @Override
    public String toString() {

        synchronized (stateLock) {
            if (this.active == false) {
                return "";
            }
        }

        /* TODO: wrap wolfSSL_X509_NAME_oneline() */
        return "";
    }

    /**
     * Free native resources of WolfSSLX509Name.
     */
    public synchronized void free() {

        synchronized (stateLock) {
            if (this.active == false) {
                /* already freed, just return */
                return;
            }

            synchronized (x509NameLock) {

                WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
                    WolfSSLDebug.INFO, this.x509NamePtr,
                    () -> "entered free()");

                /* free native resources */
                X509_NAME_free(this.x509NamePtr);

                this.active = false;
                this.x509NamePtr = 0;
            }
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void finalize() throws Throwable
    {
        this.free();
        super.finalize();
    }
}

