root/usr/src/lib/libfru/libfru/PayloadReader.cc
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <string.h>
#include <stdlib.h>

#include "PayloadReader.h"

#define ITER_CONT_BYTE_LEN 4
#define IS_ITERATED(pathDef) \
(pathDef->def->iterationType != FRU_NOT_ITERATED)

// functions to place bit data properly.
static fru_errno_t
writeBits(uint64_t bitData, size_t bitLength,
        uint8_t *data, size_t dataLength, size_t bitOffset)
{
        if ((bitLength > 64) &&
                (bitOffset > 64) &&
                (dataLength > 8) &&
                (bitOffset > (dataLength * 8)))
                return (FRU_FAILURE);
        // move the bit data into place
        bitData = (bitData << (64-bitLength));
        bitData = (bitData >> bitOffset);

        // create a mask to clear the old data.
        uint64_t mask = 0;
        for (size_t i = 0; i < bitLength; i++) {
                mask = ((mask << 1) + 1);
        }
        mask = (mask << (64-bitLength));
        mask = (mask >> bitOffset);
        mask = (mask ^ 0xFFFFFFFFFFFFFFFFULL);

        // get the data out of the byte array.
        uint64_t rd = 0;
        memcpy((void *)&rd, (void *)data, dataLength);

        // clear the old data
        rd = (rd & mask);
        // put in the new data.
        rd = (rd | bitData);

        // write the data back to the buffer.
        memcpy((void *)data, (void *)&rd, dataLength);
        return (FRU_SUCCESS);
}

static fru_errno_t
readBits(size_t bitLength, uint8_t *data,
        size_t dataLength, int bitOffset, uint64_t *ret)
{
        if ((bitLength > 64) ||
                (bitLength < 0) ||
                (bitOffset > 64) ||
                (dataLength > 8) ||
                (bitOffset > (dataLength * 8)))
                return (FRU_FAILURE);
        // get the data out of the byte array.
        uint64_t rc = 0;
        memcpy((void *)&rc, (void *)data, dataLength);

        rc = (rc << bitOffset);
        rc = (rc >> (64 - bitLength));
        *ret = rc;
        return (FRU_SUCCESS);
}

// ===========================================================================
// caller is to be sure elemDef is contained by recDef.
int
PayloadReader::getOffsetIntoRecord(fru_regdef_t *recDef,
                                fru_regdef_t *elemDef)
{
        int rc = 0;
        for (int i = 0; i < recDef->enumCount; i++) {
                if (strcmp(recDef->enumTable[i].text, elemDef->name) == 0)
                        return (rc);
                const fru_regdef_t *tmpDef = fru_reg_lookup_def_by_name(
                                        (char *)recDef->enumTable[i].text);
                rc += tmpDef->payloadLen;
        }
        return(0);
}

// ===========================================================================
// return -1 on error.
int
PayloadReader::calcOffset(int iterType,
                        uint8_t head, uint8_t tail,
                        uint8_t iterThere, uint8_t iterPoss,
                        size_t length, int index,
                        fru_errno_t *err)
{
        *err = FRU_SUCCESS;
        switch (iterType) {
                case FRU_FIFO:
                case FRU_Linear:
                {
                        if (index == PathDef::lastIteration)
                                return (length * tail);
                        return (length * index);
                break;
                }
                case FRU_Circular:
                case FRU_LIFO:
                {
                        if (index == PathDef::lastIteration) {
                                if (iterType == FRU_LIFO)
                                        return (length * head);
                                return (length * tail);
                        }

                        // For reading they are oposite.
                        if (iterType == FRU_Circular) {
                                return (length * ((head + index) % iterPoss));
                        } else {
                                int abs = tail - index;
                                if (abs < 0)
                                        // abs is negative here
                                        abs = iterPoss + abs;
                                return (length * abs);
                        }
                break;
                }
        }
        *err = FRU_FAILURE;
        return (-1);
}

// ===========================================================================
// return -1 on error.
int
PayloadReader::getIterationOffset(uint8_t *iter, int iterLen,
                                PathDef *path, int *rcIterThere,
                                fru_errno_t *err,
                                int onlyFindingIterThereFlag)
{
        int rc = 0;

        // read the iteration control bytes first because we may ONLY need
        // them.
        uint8_t head = iter[0];
        uint8_t tail = iter[1];
        uint8_t iterThere = iter[2];
        uint8_t iterPoss = iter[3];

        // the '+' symbol on anything is an error here
        if (path->iterIndex == PathDef::addIteration) {
                *err = FRU_INVALPATH;
                return (-1);
        }

        // check assumptions for next calls.
        if (iterPoss != path->def->iterationCount) {
                *err = FRU_DATACORRUPT;
                return (-1);
        }

        if (onlyFindingIterThereFlag == ITER_THERE_ONLY) {
                if (rcIterThere != NULL) {
                        *rcIterThere = iterThere;
                }
                *err = FRU_SUCCESS;
                return (ITER_CONT_BYTE_LEN);
        }

        if ((path->iterIndex != PathDef::addIteration) &&
                (path->iterIndex != PathDef::lastIteration) &&
                (path->iterIndex >= iterThere)) {
                *err = FRU_DATANOTFOUND;
                return (-1);
        }

        // don't forget to skip the iteration control bytes!!!
        int length = ((path->def->payloadLen - ITER_CONT_BYTE_LEN)
                        /path->def->iterationCount);

        rc = calcOffset(path->def->iterationType,
                        head, tail, iterThere, iterPoss,
                        length, path->iterIndex, err);
        if (rc == -1) {
                // error set by calcOffset
                return (-1);
        }

        *err = FRU_SUCCESS;
        return (ITER_CONT_BYTE_LEN + rc);
}

// ===========================================================================
// Iff onlyFindingIterThereFlag is set data is ignored and dataLen will be set
// to the number of iterations which are actually in the seeprom.
fru_errno_t
PayloadReader::readRecurse(PathDef *path,
                        uint8_t *cur, size_t curLen,
                        void **data, size_t *dataLen,
                        int onlyFindingIterThereFlag)
{
        fru_errno_t rc = FRU_SUCCESS;
        size_t calc_data_len = 0;

        if (path->next == NULL) {

                // alway go ahead and do the iterated thing.  If we are not a
                // field then the onlyFindingIterThereFlag should be set.
                // Check this afterward.
                int offset = 0;
                int iterThere = 0;
                // zzz altering the length things again...
                if (IS_ITERATED(path)) {
                        // we are iterated.
                        calc_data_len = (path->def->payloadLen
                                                -ITER_CONT_BYTE_LEN)/
                                        path->def->iterationCount;
// zzz still have to figure out the bit offsets for bit iterations...
                        offset = getIterationOffset(cur, curLen, path,
                                &iterThere, &rc,
                                onlyFindingIterThereFlag);
                        if (offset == -1)
                                return (rc);

                        // done
                        if (onlyFindingIterThereFlag) {
                                *dataLen = iterThere;
                                return (FRU_SUCCESS);
                        }
                } else {
                        // done but this thing was not an iteration!!!
                        if (onlyFindingIterThereFlag) {
                                return (FRU_INVALPATH);
                        }

                        calc_data_len = path->def->payloadLen;
                        offset = 0;
                }
                // end zzz

                // now make sure we have a field.
                if (path->def->dataType == FDTYPE_Record) {
                        return (FRU_NOTFIELD);
                }

                // allocate and copy.
                if (path->def->dataType == FDTYPE_Binary) {
                        uint64_t *eData = (uint64_t *)malloc(sizeof (*eData));
                        if (eData == NULL) {
                                return (FRU_FAILURE);
                        }

                        int bitLength = path->def->dataLength;
                        // iterated bit field adjust acordingly.
                        if (IS_ITERATED(path)) {
                                bitLength = (bitLength-(ITER_CONT_BYTE_LEN*8))/
                                        path->def->iterationCount;
                        }

                        rc = readBits(bitLength, &(cur[offset]),
                                        calc_data_len, 0, eData);
                        if (rc != FRU_SUCCESS) {
                                free(eData);
                                return (rc);
                        }
                        *data = (void *)eData;
                        *dataLen = sizeof (*eData);
                } else if (path->def->dataType == FDTYPE_Enumeration) {
                        unsigned char *eData
                                = (unsigned char *)malloc(sizeof (uint64_t));
                        if (eData == NULL) {
                                return (FRU_FAILURE);
                        }
                        /* copy the correct number of bytes to eData */
                        memset(eData, 0x00, sizeof (uint64_t));
                        memcpy(&(eData[(sizeof (uint64_t) - (calc_data_len))]),
                                &(cur[offset]),
                                (calc_data_len));
                        *data = (void*)eData;
                        *dataLen = sizeof (uint64_t);
                } else {
                        void *rc_data = malloc(calc_data_len);
                        if (rc_data == NULL) {
                                return (FRU_FAILURE);
                        }
                        memcpy(rc_data, &(cur[offset]), calc_data_len);
                        *data = rc_data;
                        *dataLen = calc_data_len;
                }

                return (FRU_SUCCESS);
        }

        // At this point we know the entry is some sort of record.

        int newOffset = 0, newLength = 0;
        if (IS_ITERATED(path)) {

// zzz still have to figure out the bit offsets for bit iterations...
                newOffset = getIterationOffset(cur, curLen,
                                path, NULL, &rc, NORMAL_READ);
                if (newOffset == -1)
                        return (rc);
        }

        newOffset += getOffsetIntoRecord(path->def, path->next->def);
        newLength = path->next->def->payloadLen;

        return (readRecurse(path->next, &(cur[newOffset]), newLength,
                data, dataLen, onlyFindingIterThereFlag));
}

// ===========================================================================
// will send the data back in (data,dataLen)
fru_errno_t
PayloadReader::readData(PathDef *path, Ancestor *curDef,
                        int instWICur,
                        uint8_t *payload, size_t payloadLen,
                        void **data, size_t *dataLen)
{
        int offset = curDef->getInstOffset(instWICur);
        return (readRecurse(path, &(payload[offset]), payloadLen-offset,
                data, dataLen, NORMAL_READ));
}

// ===========================================================================
fru_errno_t
PayloadReader::findIterThere(PathDef *path, Ancestor *curDef,
                                int instWICur,
                                uint8_t *payload, size_t payloadLen,
                                int *numThere)
{
        int offset = curDef->getInstOffset(instWICur);
        size_t tmp_num = 0;
        fru_errno_t err = readRecurse(path, &(payload[offset]),
                payloadLen-offset, NULL, &tmp_num, ITER_THERE_ONLY);

        if (err == FRU_SUCCESS) {
                int tmp_num_there = (int)tmp_num;
                if (tmp_num_there != tmp_num) {
                        return (FRU_FAILURE);
                }
                *numThere = tmp_num_there;
        }
        return (err);
}

static fru_errno_t
update_iter_cont_bytes(PathDef *path, uint8_t *cur, size_t curLen)
{
        // update the iteration control information
        uint8_t *head = &(cur[0]);
        uint8_t *tail = &(cur[1]);
        uint8_t *numThere = &(cur[2]);
        // This never changes.
        uint8_t numPoss = cur[3];

        if (numPoss != path->def->iterationCount) {
                return (FRU_DATACORRUPT);
        }

        // Remember that when the iteration is added the head and the tail both
        // equal 0 (ie point to 0).  So if we are empty when we are updating
        // then we don't have to alter the head or tail values.  We simply add
        // one to the numThere.
        if (*numThere != 0) {
                switch (path->def->iterationType) {
                        case FRU_Linear:
                                // this will flag an error when Linear can't
                                // hold anymore.
                                if ((*tail + 1) == numPoss)
                                        return (FRU_ITERFULL);
                        /* Fall through */
                        case FRU_FIFO:
                                // if we are not at the end move the tail.
                                if (*tail != (numPoss-1))
                                        *tail = *tail+1;
                        break;

                        case FRU_Circular:
                        case FRU_LIFO:
                                // this is the same except LIFO is read
                                // BACKWARDS

                                // move the tail.
                                *tail = *tail + 1;
                                // if the tail hits the end wrap around.
                                if (*tail == numPoss)
                                        *tail = 0;
                                // if tail catches head move the head.
                                if (*tail == *head) {
                                        // if head hits the end wrap around.
                                        if (++(*head) == numPoss)
                                                *head = 0;
                                }
                        break;
                }
        }
        if ((*numThere) < numPoss) {
                // add one IFF we are not full
                *numThere = *numThere + 1;
        }

        return (FRU_SUCCESS);
}

// ===========================================================================
fru_errno_t
PayloadReader::updateRecurse(PathDef *path,
                                uint8_t *cur, size_t curLen,
                                void *data, size_t dataLen)
{
        fru_errno_t rc = FRU_SUCCESS;

        if (path->next == NULL) {

                // Delay checking for Records until after this which will
                // allow for [+] notation for Iterated Records.
                // if this is updating an iteration AND we are adding one...
                if (IS_ITERATED(path) &&
                        (path->iterIndex == PathDef::addIteration)) {
                        return (update_iter_cont_bytes(path, cur, curLen));
                }

                if (path->def->dataType == FDTYPE_Record) {
                        return (FRU_NOTFIELD);
                }

                int offset = 0;
                int calcLen = 0;
                int dummy = 0;
                // zzz altering the length things again...
                if (IS_ITERATED(path)) {
                        // we are iterated.
                        calcLen = (path->def->payloadLen-ITER_CONT_BYTE_LEN)/
                                path->def->iterationCount;
// zzz still have to figure out the bit offsets
                        offset = getIterationOffset(cur, curLen,
                                path, &dummy, &rc, NORMAL_READ);
                        if (offset == -1)
                                return (rc);
                } else {
                        calcLen = path->def->payloadLen;
                        offset = 0;
                }
                // end zzz

                // once again convert enums for the user again.
                if (path->def->dataType == FDTYPE_Binary) {
                        int bitLength = path->def->dataLength;
                        // iterated bit field adjust acordingly.
                        if (path->def->iterationType != FRU_NOT_ITERATED) {
                                bitLength = (bitLength - 32)/
                                        path->def->iterationCount;
                        }

                        rc = writeBits (*(uint64_t *)data, bitLength,
                                        &(cur[offset]), calcLen, 0);
                        if (rc != FRU_SUCCESS)
                                return (rc);
                } else if (path->def->dataType == FDTYPE_Enumeration) {
                        unsigned char *tmp = (unsigned char *)data;
                        memcpy(&(cur[offset]),
                                &(tmp[(sizeof (uint64_t) - (calcLen))]),
                                calcLen);
                } else {
                        // copy into and return.
                        memcpy(&(cur[offset]), data, dataLen);
                }

                return (FRU_SUCCESS);
        }

        int newOffset = 0, newLength = 0;
        int dummy = 0;
        if (path->def->iterationType != FRU_NOT_ITERATED) {

// zzz still have to figure out the bit offsets
                newOffset = getIterationOffset(cur, curLen, path,
                                        &dummy, &rc, NORMAL_READ);
                if (newOffset == -1)
                        return (rc);
        }
        newOffset += getOffsetIntoRecord(path->def, path->next->def);
        newLength = path->next->def->payloadLen;

        return (updateRecurse(path->next, &(cur[newOffset]), newLength,
                data, dataLen));
}

// ===========================================================================
// will update the data in payload which can then be written back.
fru_errno_t
PayloadReader::updateData(PathDef *path, Ancestor *ancestorDef,
                        int instWICur,
                        uint8_t *payload, size_t payloadLen,
                        void *data, size_t dataLen)
{
        // verify the user data first before doing any major work.
        int calcLen = 0;
        PathDef *prev = path;
        PathDef *cur = path;
        while (cur != NULL) {
                prev = cur;
                cur = cur->next;
        }

        // unless we are updateing with [+] symbol
        // (which means we don't have any data length at all.)
        if (prev->iterIndex != PathDef::addIteration) {
                if (IS_ITERATED(prev)) {
                        calcLen = (prev->def->payloadLen-ITER_CONT_BYTE_LEN)/
                                prev->def->iterationCount;
                } else {
                                calcLen = prev->def->payloadLen;
                }
                // the sizeof the data for Binary or Enumeration MUST
                // be uint64_t
                if ((prev->def->dataType == FDTYPE_Enumeration) ||
                        (prev->def->dataType == FDTYPE_Binary)) {
                        if (dataLen != sizeof (uint64_t))
                                return (FRU_INVALDATASIZE);
                // all others must be shorter than the space available.
                } else {
                        if (dataLen > calcLen)
                                return (FRU_INVALDATASIZE);
                }
        }

        int offset = ancestorDef->getInstOffset(instWICur);
        return (updateRecurse(path, &(payload[offset]), payloadLen-offset,
                data, dataLen));
}