/*! Gets whether a portal has an active session.
 *  @param target the target to test for an active session.
 *  @param portal the portal to test for an active connection.
 *  @return true if the is an active connection for the portal; false otherwise. */
Boolean iSCSIDaemonIsPortalActive(iSCSIDaemonHandle handle,
                                  iSCSITargetRef target,
                                  iSCSIPortalRef portal)
{
    if(handle < 0 || !target || !portal)
        return false;

    CFDataRef targetData = iSCSITargetCreateData(target);
    CFDataRef portalData = iSCSIPortalCreateData(portal);

    iSCSIDMsgIsPortalActiveCmd cmd = iSCSIDMsgIsPortalActiveCmdInit;
    cmd.targetLength = (UInt32)CFDataGetLength(targetData);
    cmd.portalLength = (UInt32)CFDataGetLength(portalData);

    errno_t error = iSCSIDaemonSendMsg(handle,(iSCSIDMsgGeneric *)&cmd,
                                       targetData,portalData,NULL);
    CFRelease(targetData);
    CFRelease(portalData);

    if(error)
        return false;

    iSCSIDMsgIsPortalActiveRsp rsp;

    if(recv(handle,&rsp,sizeof(rsp),0) != sizeof(rsp))
        return false;

    if(rsp.funcCode != kiSCSIDIsPortalActive)
        return false;
    
    return rsp.active;
}
/*! Logs into a target using a specific portal or all portals in the database.
 *  If an argument is supplied for portal, login occurs over the specified
 *  portal.  Otherwise, the daemon will attempt to login over all portals.
 *  @param handle a handle to a daemon connection.
 *  @param authorization an authorization for the right kiSCSIAuthModifyLogin
 *  @param target specifies the target and connection parameters to use.
 *  @param portal specifies the portal to use (use NULL for all portals).
 *  @param statusCode iSCSI response code indicating operation status.
 *  @return an error code indicating whether the operation was successful. */
errno_t iSCSIDaemonLogin(iSCSIDaemonHandle handle,
                         AuthorizationRef authorization,
                         iSCSITargetRef target,
                         iSCSIPortalRef portal,
                         enum iSCSILoginStatusCode * statusCode)
{
    if(handle < 0 || !target || !authorization || !statusCode)
        return EINVAL;

    CFDataRef targetData = iSCSITargetCreateData(target);
    CFDataRef portalData = NULL;

    iSCSIDMsgLoginCmd cmd = iSCSIDMsgLoginCmdInit;
    cmd.authLength = kAuthorizationExternalFormLength;
    cmd.targetLength = (UInt32)CFDataGetLength(targetData);
    cmd.portalLength = 0;

    if(portal) {
        portalData = iSCSIPortalCreateData(portal);
        cmd.portalLength = (UInt32)CFDataGetLength(portalData);
    }
    
    AuthorizationExternalForm authExtForm;
    AuthorizationMakeExternalForm(authorization,&authExtForm);
    
    CFDataRef authData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
                                                     (UInt8*)&authExtForm.bytes,
                                                     kAuthorizationExternalFormLength,
                                                     kCFAllocatorDefault);

    errno_t error = iSCSIDaemonSendMsg(handle,(iSCSIDMsgGeneric *)&cmd,
                                       authData,targetData,portalData,NULL);
    
    if(portal)
        CFRelease(portalData);
    CFRelease(targetData);

    if(error)
        return error;

    iSCSIDMsgLoginRsp rsp;

    if(recv(handle,&rsp,sizeof(rsp),0) != sizeof(rsp))
        return EIO;

    if(rsp.funcCode != kiSCSIDLogin)
        return EIO;

    // At this point we have a valid response, process it
    *statusCode = rsp.statusCode;

    return rsp.errorCode;
}
/*! Sets or updates a shared secret.
 *  @param handle a handle to a daemon connection.
 *  @param authorization an authorization for the right kiSCSIAuthModifyRights.
 *  @param nodeIQN the node iSCSI qualified name.
 *  @param sharedSecret the secret to set.
 *  @return an error code indicating whether the operating was successful. */
errno_t iSCSIDaemonSetSharedSecret(iSCSIDaemonHandle handle,
                                   AuthorizationRef authorization,
                                   CFStringRef nodeIQN,
                                   CFStringRef sharedSecret)
{
    // Validate inputs
    if(handle < 0 || !authorization || !nodeIQN || !sharedSecret)
        return EINVAL;
    
    AuthorizationExternalForm authExtForm;
    AuthorizationMakeExternalForm(authorization,&authExtForm);
    
    CFDataRef authData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
                                                     (UInt8*)&authExtForm.bytes,
                                                     kAuthorizationExternalFormLength,
                                                     kCFAllocatorDefault);
    
    CFDataRef nodeIQNData = CFStringCreateExternalRepresentation(kCFAllocatorDefault,nodeIQN,kCFStringEncodingASCII,0);
    CFDataRef sharedSecretData = CFStringCreateExternalRepresentation(kCFAllocatorDefault,sharedSecret,kCFStringEncodingASCII,0);
    
    iSCSIDMsgSetSharedSecretCmd cmd = iSCSIDMsgSetSharedSecretCmdInit;
    cmd.authorizationLength = (UInt32)CFDataGetLength(authData);
    cmd.nodeIQNLength = (UInt32)CFDataGetLength(nodeIQNData);
    cmd.secretLength = (UInt32)CFDataGetLength(sharedSecretData);
    
    errno_t error = iSCSIDaemonSendMsg(handle,(iSCSIDMsgGeneric *)&cmd,
                                       authData,nodeIQNData,sharedSecretData,NULL);
    
    if(nodeIQNData)
        CFRelease(nodeIQNData);
    
    if(sharedSecretData)
        CFRelease(sharedSecretData);

    if(error)
        return error;
    
    iSCSIDMsgSetSharedSecretRsp rsp;
    
    if(recv(handle,&rsp,sizeof(rsp),0) != sizeof(rsp))
        return EIO;
    
    if(rsp.funcCode != kiSCSIDSetSharedSecret)
        return EIO;

    return rsp.errorCode;
}
/*! Retrieves a list of targets available from a give portal.
 *  @param handle a handle to a daemon connection.
 *  @param portal the iSCSI portal to look for targets.
 *  @param authMethod the preferred authentication method.
 *  @param statusCode iSCSI response code indicating operation status.
 *  @return an error code indicating whether the operation was successful. */
errno_t iSCSIDaemonQueryTargetForAuthMethod(iSCSIDaemonHandle handle,
                                            iSCSIPortalRef portal,
                                            CFStringRef targetIQN,
                                            enum iSCSIAuthMethods * authMethod,
                                            enum iSCSILoginStatusCode * statusCode)
{
    // Validate inputs
    if(handle < 0 || !portal || !targetIQN || !authMethod || !statusCode)
        return EINVAL;
    
    // Setup a target object with the target name
    iSCSIMutableTargetRef target = iSCSITargetCreateMutable();
    iSCSITargetSetIQN(target,targetIQN);
    
    // Generate data to transmit (no longer need target object after this)
    CFDataRef targetData = iSCSITargetCreateData(target);
    iSCSITargetRelease(target);
    
    CFDataRef portalData = iSCSIPortalCreateData(portal);
    
    // Create command header to transmit
    iSCSIDMsgQueryTargetForAuthMethodCmd cmd = iSCSIDMsgQueryTargetForAuthMethodCmdInit;
    cmd.portalLength = (UInt32)CFDataGetLength(portalData);
    cmd.targetLength = (UInt32)CFDataGetLength(targetData);
    
    if(iSCSIDaemonSendMsg(handle,(iSCSIDMsgGeneric *)&cmd,targetData,portalData,NULL))
    {
        CFRelease(portalData);
        CFRelease(targetData);
        return EIO;
    }

    CFRelease(portalData);
    CFRelease(targetData);
    iSCSIDMsgQueryTargetForAuthMethodRsp rsp;
    
    if(recv(handle,&rsp,sizeof(rsp),0) != sizeof(rsp))
        return EIO;
    
    *authMethod = rsp.authMethod;
    *statusCode = rsp.statusCode;
    
    return rsp.errorCode;
}
/*! Logs out of the target or a specific portal, if specified.
 *  @param handle a handle to a daemon connection.
 *  @param target the target to logout.
 *  @param portal the portal to logout.  If one is not specified,
 *  @param statusCode iSCSI response code indicating operation status. */
errno_t iSCSIDaemonLogout(iSCSIDaemonHandle handle,
                          iSCSITargetRef target,
                          iSCSIPortalRef portal,
                          enum iSCSILogoutStatusCode * statusCode)
{
    if(handle < 0 || !target || !statusCode)
        return EINVAL;

    CFDataRef targetData = iSCSITargetCreateData(target);
    CFDataRef portalData = NULL;

    iSCSIDMsgLogoutCmd cmd = iSCSIDMsgLogoutCmdInit;
    cmd.targetLength = (UInt32)CFDataGetLength(targetData);
    cmd.portalLength = 0;

    if(portal) {
        portalData = iSCSIPortalCreateData(portal);
        cmd.portalLength = (UInt32)CFDataGetLength(portalData);
    }

    errno_t error = iSCSIDaemonSendMsg(handle,(iSCSIDMsgGeneric *)&cmd,
                                       targetData,portalData,NULL);
    if(portal)
        CFRelease(portalData);
    CFRelease(targetData);

    if(error)
        return error;

    iSCSIDMsgLogoutRsp rsp;

    if(recv(handle,&rsp,sizeof(rsp),0) != sizeof(rsp))
        return EIO;

    if(rsp.funcCode != kiSCSIDLogout)
        return EIO;

    // At this point we have a valid response, process it
    *statusCode = rsp.statusCode;
    
    return rsp.errorCode;
}
/*! Semaphore that allows a client exclusive accesss to the property list
 *  that contains iSCSI configuraiton parameters and targets. Forces the provided
 *  preferences object to synchronize with property list on the disk.
 *  @param handle a handle to a daemon connection.
 *  @param authorization an authorization for the right kiSCSIAuthModifyRights
 *  @param preferences the preferences to be synchronized
 *  @return an error code indicating whether the operating was successful. */
errno_t iSCSIDaemonPreferencesIOLockAndSync(iSCSIDaemonHandle handle,
                                            AuthorizationRef authorization,
                                            iSCSIPreferencesRef preferences)
{
    // Validate inputs
    if(handle < 0 || !authorization || !preferences)
        return EINVAL;

    //  Send in authorization and acquire lock
    AuthorizationExternalForm authExtForm;
    AuthorizationMakeExternalForm(authorization,&authExtForm);
    
    CFDataRef authData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
                                                     (UInt8*)authExtForm.bytes,
                                                     kAuthorizationExternalFormLength,
                                                     kCFAllocatorDefault);
    
    iSCSIDMsgPreferencesIOLockAndSyncCmd cmd = iSCSIDMsgPreferencesIOLockAndSyncCmdInit;
    cmd.authorizationLength = (UInt32)CFDataGetLength(authData);
    
    errno_t error = iSCSIDaemonSendMsg(handle,(iSCSIDMsgGeneric *)&cmd,authData,NULL);
    
    if(error)
        return error;
    
    iSCSIDMsgPreferencesIOLockAndSyncRsp rsp;
    
    if(recv(handle,&rsp,sizeof(rsp),0) != sizeof(rsp))
        return EIO;
    
    if(rsp.funcCode != kiSCSIDPreferencesIOLockAndSync)
        return EIO;
    
    if(rsp.errorCode == 0) {
        // Force preferences to synchronize after obtaining lock
        // (this ensures that the client has the most up-to-date preferences data)
        iSCSIPreferencesUpdateWithAppValues(preferences);
    }
    
    return rsp.errorCode;
}
/*! Creates a dictionary of connection parameters for the connection associated
 *  with the specified target and portal, if one exists.
 *  @param handle a handle to a daemon connection.
 *  @param target the target associated with the the specified portal.
 *  @param portal the portal to check for active connections to generate
 *  a dictionary of connection parameters.
 *  @return a dictionary of connection properties. */
CFDictionaryRef iSCSIDaemonCreateCFPropertiesForConnection(iSCSIDaemonHandle handle,
                                                           iSCSITargetRef target,
                                                           iSCSIPortalRef portal)
{
    // Validate inputs
    if(handle < 0 || !target || !portal)
        return NULL;

    CFDictionaryRef properties = NULL;
    CFDataRef targetData = iSCSITargetCreateData(target);
    CFDataRef portalData = iSCSIPortalCreateData(portal);

    // Send command to daemon
    iSCSIDMsgCreateCFPropertiesForConnectionCmd cmd = iSCSIDMsgCreateCFPropertiesForConnectionCmdInit;

    cmd.targetLength = (UInt32)CFDataGetLength(targetData);
    cmd.portalLength = (UInt32)CFDataGetLength(portalData);

    errno_t error = iSCSIDaemonSendMsg(handle,(iSCSIDMsgGeneric *)&cmd,
                                       targetData,portalData,NULL);
    CFRelease(targetData);
    CFRelease(portalData);

    iSCSIDMsgCreateCFPropertiesForConnectionRsp rsp;
    
    if(!error)
        error = iSCSIDaemonRecvMsg(handle,(iSCSIDMsgGeneric*)&rsp,NULL);
    
    if(!error) {
        CFDataRef data = NULL;
        error = iSCSIDaemonRecvMsg(handle,0,&data,rsp.dataLength,NULL);
        
        if(!error && data) {
            CFPropertyListFormat format;
            properties = CFPropertyListCreateWithData(kCFAllocatorDefault,data,0,&format,NULL);
            CFRelease(data);
        }
    }
    return properties;
}
/*! Synchronizes cached preference changes to disk and releases the locked
 *  semaphore, allowing other clients to make changes. If the prefereneces
 *  parameter is NULL, then no changes are made to disk and the semaphore is
 *  unlocked.
 *  @param handle a handle to a daemon connection.
 *  @param preferences the preferences to be synchronized
 *  @return an error code indicating whether the operating was successful. */
errno_t iSCSIDaemonPreferencesIOUnlockAndSync(iSCSIDaemonHandle handle,
                                              iSCSIPreferencesRef preferences)
{
    // Validate inputs
    if(handle < 0)
        return EINVAL;
    
    CFDataRef preferencesData = NULL;
    
    iSCSIDMsgPreferencesIOUnlockAndSyncCmd cmd = iSCSIDMsgPreferencesIOUnlockAndSyncCmdInit;
    
    if(preferences) {
        preferencesData = iSCSIPreferencesCreateData(preferences);
        cmd.preferencesLength = (UInt32)CFDataGetLength(preferencesData);
    }
    else
        cmd.preferencesLength = 0;
    
    errno_t error = iSCSIDaemonSendMsg(handle,(iSCSIDMsgGeneric *)&cmd,preferencesData,NULL);
    
    if(preferencesData)
        CFRelease(preferencesData);
    
    if(error)
        return error;
    
    iSCSIDMsgPreferencesIOUnlockAndSyncRsp rsp;
    
    if(recv(handle,&rsp,sizeof(rsp),0) != sizeof(rsp))
        return EIO;
    
    if(rsp.funcCode != kiSCSIDPreferencesIOUnlockAndSync)
        return EIO;
    
    return rsp.errorCode;
}