diff options
author | Anatol Pomozov <anatol.pomozov@gmail.com> | 2012-03-28 09:12:55 -0700 |
---|---|---|
committer | Anatol Pomozov <anatol.pomozov@gmail.com> | 2012-03-28 12:02:47 -0700 |
commit | b0b2b4d890cf3bfb274797a759642b4e733343d7 (patch) | |
tree | 12ad21cbad346f02d542aa4d672ffd76407d58a9 /drm/libdrmframework/plugins/forward-lock/internal-format/decoder | |
parent | 51f8eec23a2bcc2cc190373cdd1195972d9b8804 (diff) | |
parent | 5a5491c17d74bd2c80cf451c6ddbba22d5d5f08a (diff) | |
download | frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.zip frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.tar.gz frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.tar.bz2 |
Merge media files with history from frameworks/base.git
Diffstat (limited to 'drm/libdrmframework/plugins/forward-lock/internal-format/decoder')
3 files changed, 626 insertions, 0 deletions
diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.mk b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.mk new file mode 100644 index 0000000..b625edf --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.mk @@ -0,0 +1,37 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + FwdLockFile.c + +LOCAL_C_INCLUDES := \ + frameworks/base/drm/libdrmframework/plugins/forward-lock/internal-format/common \ + external/openssl/include + +LOCAL_SHARED_LIBRARIES := libcrypto + +LOCAL_WHOLE_STATIC_LIBRARIES := libfwdlock-common + +LOCAL_STATIC_LIBRARIES := libfwdlock-common + +LOCAL_MODULE := libfwdlock-decoder + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_LIBRARY) diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.c b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.c new file mode 100644 index 0000000..365bdec --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.c @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/Log.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <openssl/aes.h> +#include <openssl/hmac.h> + +#include "FwdLockFile.h" +#include "FwdLockGlue.h" + +#define TRUE 1 +#define FALSE 0 + +#define INVALID_OFFSET ((off64_t)-1) + +#define INVALID_BLOCK_INDEX ((uint64_t)-1) + +#define MAX_NUM_SESSIONS 128 + +#define KEY_SIZE AES_BLOCK_SIZE +#define KEY_SIZE_IN_BITS (KEY_SIZE * 8) + +#define SHA1_HASH_SIZE 20 +#define SHA1_BLOCK_SIZE 64 + +#define FWD_LOCK_VERSION 0 +#define FWD_LOCK_SUBFORMAT 0 +#define USAGE_RESTRICTION_FLAGS 0 +#define CONTENT_TYPE_LENGTH_POS 7 +#define TOP_HEADER_SIZE 8 + +#define SIG_CALC_BUFFER_SIZE (16 * SHA1_BLOCK_SIZE) + +/** + * Data type for the per-file state information needed by the decoder. + */ +typedef struct FwdLockFile_Session { + int fileDesc; + unsigned char topHeader[TOP_HEADER_SIZE]; + char *pContentType; + size_t contentTypeLength; + void *pEncryptedSessionKey; + size_t encryptedSessionKeyLength; + unsigned char dataSignature[SHA1_HASH_SIZE]; + unsigned char headerSignature[SHA1_HASH_SIZE]; + off64_t dataOffset; + off64_t filePos; + AES_KEY encryptionRoundKeys; + HMAC_CTX signingContext; + unsigned char keyStream[AES_BLOCK_SIZE]; + uint64_t blockIndex; +} FwdLockFile_Session_t; + +static FwdLockFile_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL }; + +static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER; + +static const unsigned char topHeaderTemplate[] = + { 'F', 'W', 'L', 'K', FWD_LOCK_VERSION, FWD_LOCK_SUBFORMAT, USAGE_RESTRICTION_FLAGS }; + +/** + * Acquires an unused file session for the given file descriptor. + * + * @param[in] fileDesc A file descriptor. + * + * @return A session ID. + */ +static int FwdLockFile_AcquireSession(int fileDesc) { + int sessionId = -1; + if (fileDesc < 0) { + errno = EBADF; + } else { + int i; + pthread_mutex_lock(&sessionAcquisitionMutex); + for (i = 0; i < MAX_NUM_SESSIONS; ++i) { + int candidateSessionId = (fileDesc + i) % MAX_NUM_SESSIONS; + if (sessionPtrs[candidateSessionId] == NULL) { + sessionPtrs[candidateSessionId] = malloc(sizeof **sessionPtrs); + if (sessionPtrs[candidateSessionId] != NULL) { + sessionPtrs[candidateSessionId]->fileDesc = fileDesc; + sessionPtrs[candidateSessionId]->pContentType = NULL; + sessionPtrs[candidateSessionId]->pEncryptedSessionKey = NULL; + sessionId = candidateSessionId; + } + break; + } + } + pthread_mutex_unlock(&sessionAcquisitionMutex); + if (i == MAX_NUM_SESSIONS) { + ALOGE("Too many sessions opened at the same time"); + errno = ENFILE; + } + } + return sessionId; +} + +/** + * Finds the file session associated with the given file descriptor. + * + * @param[in] fileDesc A file descriptor. + * + * @return A session ID. + */ +static int FwdLockFile_FindSession(int fileDesc) { + int sessionId = -1; + if (fileDesc < 0) { + errno = EBADF; + } else { + int i; + pthread_mutex_lock(&sessionAcquisitionMutex); + for (i = 0; i < MAX_NUM_SESSIONS; ++i) { + int candidateSessionId = (fileDesc + i) % MAX_NUM_SESSIONS; + if (sessionPtrs[candidateSessionId] != NULL && + sessionPtrs[candidateSessionId]->fileDesc == fileDesc) { + sessionId = candidateSessionId; + break; + } + } + pthread_mutex_unlock(&sessionAcquisitionMutex); + if (i == MAX_NUM_SESSIONS) { + errno = EBADF; + } + } + return sessionId; +} + +/** + * Releases a file session. + * + * @param[in] sessionID A session ID. + */ +static void FwdLockFile_ReleaseSession(int sessionId) { + pthread_mutex_lock(&sessionAcquisitionMutex); + assert(0 <= sessionId && sessionId < MAX_NUM_SESSIONS && sessionPtrs[sessionId] != NULL); + free(sessionPtrs[sessionId]->pContentType); + free(sessionPtrs[sessionId]->pEncryptedSessionKey); + memset(sessionPtrs[sessionId], 0, sizeof *sessionPtrs[sessionId]); // Zero out key data. + free(sessionPtrs[sessionId]); + sessionPtrs[sessionId] = NULL; + pthread_mutex_unlock(&sessionAcquisitionMutex); +} + +/** + * Derives keys for encryption and signing from the encrypted session key. + * + * @param[in,out] pSession A reference to a file session. + * + * @return A Boolean value indicating whether key derivation was successful. + */ +static int FwdLockFile_DeriveKeys(FwdLockFile_Session_t * pSession) { + int result; + struct FwdLockFile_DeriveKeys_Data { + AES_KEY sessionRoundKeys; + unsigned char value[KEY_SIZE]; + unsigned char key[KEY_SIZE]; + } *pData = malloc(sizeof *pData); + if (pData == NULL) { + result = FALSE; + } else { + result = FwdLockGlue_DecryptKey(pSession->pEncryptedSessionKey, + pSession->encryptedSessionKeyLength, pData->key, KEY_SIZE); + if (result) { + if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, &pData->sessionRoundKeys) != 0) { + result = FALSE; + } else { + // Encrypt the 16-byte value {0, 0, ..., 0} to produce the encryption key. + memset(pData->value, 0, KEY_SIZE); + AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys); + if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, + &pSession->encryptionRoundKeys) != 0) { + result = FALSE; + } else { + // Encrypt the 16-byte value {1, 0, ..., 0} to produce the signing key. + ++pData->value[0]; + AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys); + HMAC_CTX_init(&pSession->signingContext); + HMAC_Init_ex(&pSession->signingContext, pData->key, KEY_SIZE, EVP_sha1(), NULL); + } + } + } + if (!result) { + errno = ENOSYS; + } + memset(pData, 0, sizeof pData); // Zero out key data. + free(pData); + } + return result; +} + +/** + * Calculates the counter, treated as a 16-byte little-endian number, used to generate the keystream + * for the given block. + * + * @param[in] pNonce A reference to the nonce. + * @param[in] blockIndex The index number of the block. + * @param[out] pCounter A reference to the counter. + */ +static void FwdLockFile_CalculateCounter(const unsigned char *pNonce, + uint64_t blockIndex, + unsigned char *pCounter) { + unsigned char carry = 0; + size_t i = 0; + for (; i < sizeof blockIndex; ++i) { + unsigned char part = pNonce[i] + (unsigned char)(blockIndex >> (i * CHAR_BIT)); + pCounter[i] = part + carry; + carry = (part < pNonce[i] || pCounter[i] < part) ? 1 : 0; + } + for (; i < AES_BLOCK_SIZE; ++i) { + pCounter[i] = pNonce[i] + carry; + carry = (pCounter[i] < pNonce[i]) ? 1 : 0; + } +} + +/** + * Decrypts the byte at the current file position using AES-128-CTR. In CTR (or "counter") mode, + * encryption and decryption are performed using the same algorithm. + * + * @param[in,out] pSession A reference to a file session. + * @param[in] pByte The byte to decrypt. + */ +void FwdLockFile_DecryptByte(FwdLockFile_Session_t * pSession, unsigned char *pByte) { + uint64_t blockIndex = pSession->filePos / AES_BLOCK_SIZE; + uint64_t blockOffset = pSession->filePos % AES_BLOCK_SIZE; + if (blockIndex != pSession->blockIndex) { + // The first 16 bytes of the encrypted session key is used as the nonce. + unsigned char counter[AES_BLOCK_SIZE]; + FwdLockFile_CalculateCounter(pSession->pEncryptedSessionKey, blockIndex, counter); + AES_encrypt(counter, pSession->keyStream, &pSession->encryptionRoundKeys); + pSession->blockIndex = blockIndex; + } + *pByte ^= pSession->keyStream[blockOffset]; +} + +int FwdLockFile_attach(int fileDesc) { + int sessionId = FwdLockFile_AcquireSession(fileDesc); + if (sessionId >= 0) { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + int isSuccess = FALSE; + if (read(fileDesc, pSession->topHeader, TOP_HEADER_SIZE) == TOP_HEADER_SIZE && + memcmp(pSession->topHeader, topHeaderTemplate, sizeof topHeaderTemplate) == 0) { + pSession->contentTypeLength = pSession->topHeader[CONTENT_TYPE_LENGTH_POS]; + assert(pSession->contentTypeLength <= UCHAR_MAX); // Untaint scalar for code checkers. + pSession->pContentType = malloc(pSession->contentTypeLength + 1); + if (pSession->pContentType != NULL && + read(fileDesc, pSession->pContentType, pSession->contentTypeLength) == + (ssize_t)pSession->contentTypeLength) { + pSession->pContentType[pSession->contentTypeLength] = '\0'; + pSession->encryptedSessionKeyLength = FwdLockGlue_GetEncryptedKeyLength(KEY_SIZE); + pSession->pEncryptedSessionKey = malloc(pSession->encryptedSessionKeyLength); + if (pSession->pEncryptedSessionKey != NULL && + read(fileDesc, pSession->pEncryptedSessionKey, + pSession->encryptedSessionKeyLength) == + (ssize_t)pSession->encryptedSessionKeyLength && + read(fileDesc, pSession->dataSignature, SHA1_HASH_SIZE) == + SHA1_HASH_SIZE && + read(fileDesc, pSession->headerSignature, SHA1_HASH_SIZE) == + SHA1_HASH_SIZE) { + isSuccess = FwdLockFile_DeriveKeys(pSession); + } + } + } + if (isSuccess) { + pSession->dataOffset = pSession->contentTypeLength + + pSession->encryptedSessionKeyLength + TOP_HEADER_SIZE + 2 * SHA1_HASH_SIZE; + pSession->filePos = 0; + pSession->blockIndex = INVALID_BLOCK_INDEX; + } else { + FwdLockFile_ReleaseSession(sessionId); + sessionId = -1; + } + } + return (sessionId >= 0) ? 0 : -1; +} + +int FwdLockFile_open(const char *pFilename) { + int fileDesc = open(pFilename, O_RDONLY); + if (fileDesc < 0) { + ALOGE("failed to open file '%s': %s", pFilename, strerror(errno)); + return fileDesc; + } + + if (FwdLockFile_attach(fileDesc) < 0) { + (void)close(fileDesc); + fileDesc = -1; + } + return fileDesc; +} + +ssize_t FwdLockFile_read(int fileDesc, void *pBuffer, size_t numBytes) { + ssize_t numBytesRead; + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + numBytesRead = -1; + } else { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + ssize_t i; + numBytesRead = read(pSession->fileDesc, pBuffer, numBytes); + for (i = 0; i < numBytesRead; ++i) { + FwdLockFile_DecryptByte(pSession, &((unsigned char *)pBuffer)[i]); + ++pSession->filePos; + } + } + return numBytesRead; +} + +off64_t FwdLockFile_lseek(int fileDesc, off64_t offset, int whence) { + off64_t newFilePos; + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + newFilePos = INVALID_OFFSET; + } else { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + switch (whence) { + case SEEK_SET: + newFilePos = lseek64(pSession->fileDesc, pSession->dataOffset + offset, whence); + break; + case SEEK_CUR: + case SEEK_END: + newFilePos = lseek64(pSession->fileDesc, offset, whence); + break; + default: + errno = EINVAL; + newFilePos = INVALID_OFFSET; + break; + } + if (newFilePos != INVALID_OFFSET) { + if (newFilePos < pSession->dataOffset) { + // The new file position is illegal for an internal Forward Lock file. Restore the + // original file position. + (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos, + SEEK_SET); + errno = EINVAL; + newFilePos = INVALID_OFFSET; + } else { + // The return value should be the file position that lseek64() would have returned + // for the embedded content file. + pSession->filePos = newFilePos - pSession->dataOffset; + newFilePos = pSession->filePos; + } + } + } + return newFilePos; +} + +int FwdLockFile_detach(int fileDesc) { + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + return -1; + } + HMAC_CTX_cleanup(&sessionPtrs[sessionId]->signingContext); + FwdLockFile_ReleaseSession(sessionId); + return 0; +} + +int FwdLockFile_close(int fileDesc) { + return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1; +} + +int FwdLockFile_CheckDataIntegrity(int fileDesc) { + int result; + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + result = FALSE; + } else { + struct FwdLockFile_CheckDataIntegrity_Data { + unsigned char signature[SHA1_HASH_SIZE]; + unsigned char buffer[SIG_CALC_BUFFER_SIZE]; + } *pData = malloc(sizeof *pData); + if (pData == NULL) { + result = FALSE; + } else { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + if (lseek64(pSession->fileDesc, pSession->dataOffset, SEEK_SET) != + pSession->dataOffset) { + result = FALSE; + } else { + ssize_t numBytesRead; + unsigned int signatureSize = SHA1_HASH_SIZE; + while ((numBytesRead = + read(pSession->fileDesc, pData->buffer, SIG_CALC_BUFFER_SIZE)) > 0) { + HMAC_Update(&pSession->signingContext, pData->buffer, (size_t)numBytesRead); + } + if (numBytesRead < 0) { + result = FALSE; + } else { + HMAC_Final(&pSession->signingContext, pData->signature, &signatureSize); + assert(signatureSize == SHA1_HASH_SIZE); + result = memcmp(pData->signature, pSession->dataSignature, SHA1_HASH_SIZE) == 0; + } + HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL); + (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos, + SEEK_SET); + } + free(pData); + } + } + return result; +} + +int FwdLockFile_CheckHeaderIntegrity(int fileDesc) { + int result; + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + result = FALSE; + } else { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + unsigned char signature[SHA1_HASH_SIZE]; + unsigned int signatureSize = SHA1_HASH_SIZE; + HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE); + HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->pContentType, + pSession->contentTypeLength); + HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey, + pSession->encryptedSessionKeyLength); + HMAC_Update(&pSession->signingContext, pSession->dataSignature, SHA1_HASH_SIZE); + HMAC_Final(&pSession->signingContext, signature, &signatureSize); + assert(signatureSize == SHA1_HASH_SIZE); + result = memcmp(signature, pSession->headerSignature, SHA1_HASH_SIZE) == 0; + HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL); + } + return result; +} + +int FwdLockFile_CheckIntegrity(int fileDesc) { + return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc); +} + +const char *FwdLockFile_GetContentType(int fileDesc) { + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + return NULL; + } + return sessionPtrs[sessionId]->pContentType; +} diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.h b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.h new file mode 100644 index 0000000..fc64050 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FWDLOCKFILE_H__ +#define __FWDLOCKFILE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +/** + * Attaches to an open Forward Lock file. The file position is assumed to be at the beginning of the + * file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A status code. + * @retval 0 Success. + * @retval -1 Failure. + */ +int FwdLockFile_attach(int fileDesc); + +/** + * Opens a Forward Lock file for reading. + * + * @param[in] pFilename A reference to a filename. + * + * @return A file descriptor. + * @retval -1 Failure. + */ +int FwdLockFile_open(const char *pFilename); + +/** + * Reads the specified number of bytes from an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * @param[out] pBuffer A reference to the buffer that should receive the read data. + * @param[in] numBytes The number of bytes to read. + * + * @return The number of bytes read. + * @retval -1 Failure. + */ +ssize_t FwdLockFile_read(int fileDesc, void *pBuffer, size_t numBytes); + +/** + * Updates the file position within an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * @param[in] offset The offset with which to update the file position. + * @param[in] whence One of SEEK_SET, SEEK_CUR, and SEEK_END. + * + * @return The new file position. + * @retval ((off64_t)-1) Failure. + */ +off64_t FwdLockFile_lseek(int fileDesc, off64_t offset, int whence); + +/** + * Detaches from an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A status code. + * @retval 0 Success. + * @retval -1 Failure. + */ +int FwdLockFile_detach(int fileDesc); + +/** + * Closes an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A status code. + * @retval 0 Success. + * @retval -1 Failure. + */ +int FwdLockFile_close(int fileDesc); + +/** + * Checks the data integrity of an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A Boolean value indicating whether the integrity check was successful. + */ +int FwdLockFile_CheckDataIntegrity(int fileDesc); + +/** + * Checks the header integrity of an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A Boolean value indicating whether the integrity check was successful. + */ +int FwdLockFile_CheckHeaderIntegrity(int fileDesc); + +/** + * Checks both the data and header integrity of an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A Boolean value indicating whether the integrity check was successful. + */ +int FwdLockFile_CheckIntegrity(int fileDesc); + +/** + * Returns the content type of an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return + * A reference to the content type. The reference remains valid as long as the file is kept open. + */ +const char *FwdLockFile_GetContentType(int fileDesc); + +#ifdef __cplusplus +} +#endif + +#endif // __FWDLOCKFILE_H__ |