summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp')
-rw-r--r--src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp1860
1 files changed, 1860 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp b/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp
new file mode 100644
index 00000000..e2e6e199
--- /dev/null
+++ b/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp
@@ -0,0 +1,1860 @@
+/* $Id: USBFilter.cpp $ */
+/** @file
+ * VirtualBox USB filter abstraction.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/usbfilter.h>
+#include <VBox/usblib.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+
+
+/** @todo split this up for the sake of device drivers and such. */
+
+
+/**
+ * Initializes an USBFILTER structure.
+ *
+ * @param pFilter The filter to initialize.
+ * @param enmType The filter type. If not valid, the filter will not
+ * be properly initialized and all other calls will fail.
+ */
+USBLIB_DECL(void) USBFilterInit(PUSBFILTER pFilter, USBFILTERTYPE enmType)
+{
+ memset(pFilter, 0, sizeof(*pFilter));
+ AssertReturnVoid(enmType > USBFILTERTYPE_INVALID && enmType < USBFILTERTYPE_END);
+ pFilter->u32Magic = USBFILTER_MAGIC;
+ pFilter->enmType = enmType;
+ for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++)
+ pFilter->aFields[i].enmMatch = USBFILTERMATCH_IGNORE;
+}
+
+
+/**
+ * Make a clone of the specified filter.
+ *
+ * @param pFilter The target filter.
+ * @param pToClone The source filter.
+ */
+USBLIB_DECL(void) USBFilterClone(PUSBFILTER pFilter, PCUSBFILTER pToClone)
+{
+ memcpy(pFilter, pToClone, sizeof(*pToClone));
+}
+
+
+/**
+ * Deletes (invalidates) an USBFILTER structure.
+ *
+ * @param pFilter The filter to delete.
+ */
+USBLIB_DECL(void) USBFilterDelete(PUSBFILTER pFilter)
+{
+ pFilter->u32Magic = ~USBFILTER_MAGIC;
+ pFilter->enmType = USBFILTERTYPE_INVALID;
+ pFilter->offCurEnd = 0xfffff;
+}
+
+
+/**
+ * Skips blanks.
+ *
+ * @returns Next non-blank char in the string.
+ * @param psz The string.
+ */
+DECLINLINE(const char *) usbfilterSkipBlanks(const char *psz)
+{
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+ return psz;
+}
+
+
+/**
+ * Worker for usbfilterReadNumber that parses a hexadecimal number.
+ *
+ * @returns Same as usbfilterReadNumber, except for VERR_NO_DIGITS.
+ * @param pszExpr Where to start converting, first char is a valid digit.
+ * @param ppszExpr See usbfilterReadNumber.
+ * @param pu16Val See usbfilterReadNumber.
+ */
+static int usbfilterReadNumberHex(const char *pszExpr, const char **ppszExpr, uint16_t *pu16Val)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t u32 = 0;
+ do
+ {
+ unsigned uDigit = *pszExpr >= 'a' && *pszExpr <= 'f'
+ ? *pszExpr - 'a' + 10
+ : *pszExpr >= 'A' && *pszExpr <= 'F'
+ ? *pszExpr - 'A' + 10
+ : *pszExpr - '0';
+ if (uDigit >= 16)
+ break;
+ u32 *= 16;
+ u32 += uDigit;
+ if (u32 > UINT16_MAX)
+ rc = VWRN_NUMBER_TOO_BIG;
+ } while (*++pszExpr);
+
+ *ppszExpr = usbfilterSkipBlanks(pszExpr);
+ *pu16Val = rc == VINF_SUCCESS ? u32 : UINT16_MAX;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for usbfilterReadNumber that parses a decimal number.
+ *
+ * @returns Same as usbfilterReadNumber, except for VERR_NO_DIGITS.
+ * @param pszExpr Where to start converting, first char is a valid digit.
+ * @param uBase The base - 8 or 16.
+ * @param ppszExpr See usbfilterReadNumber.
+ * @param pu16Val See usbfilterReadNumber.
+ */
+static int usbfilterReadNumberDecimal(const char *pszExpr, unsigned uBase, const char **ppszExpr, uint16_t *pu16Val)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t u32 = 0;
+ do
+ {
+ unsigned uDigit = *pszExpr - '0';
+ if (uDigit >= uBase)
+ break;
+ u32 *= uBase;
+ u32 += uDigit;
+ if (u32 > UINT16_MAX)
+ rc = VWRN_NUMBER_TOO_BIG;
+ } while (*++pszExpr);
+
+ *ppszExpr = usbfilterSkipBlanks(pszExpr);
+ *pu16Val = rc == VINF_SUCCESS ? u32 : UINT16_MAX;
+ return rc;
+}
+
+
+/**
+ * Reads a number from a numeric expression.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS if all is fine. *ppszExpr and *pu16Val are updated.
+ * @retval VWRN_NUMBER_TOO_BIG if the number exceeds unsigned 16-bit, both *ppszExpr and *pu16Val are updated.
+ * @retval VERR_NO_DIGITS if there aren't any digits.
+ *
+ * @param ppszExpr Pointer to the current expression pointer.
+ * This is advanced past the expression and trailing blanks on success.
+ * @param pu16Val Where to store the value on success.
+ */
+static int usbfilterReadNumber(const char **ppszExpr, uint16_t *pu16Val)
+{
+ const char *pszExpr = usbfilterSkipBlanks(*ppszExpr);
+ if (!RT_C_IS_DIGIT(*pszExpr))
+ return VERR_NO_DIGITS;
+
+ if (*pszExpr == '0')
+ {
+ if (pszExpr[1] == 'x' || pszExpr[1] == 'X')
+ {
+ if (!RT_C_IS_XDIGIT(pszExpr[2]))
+ return VERR_NO_DIGITS;
+ return usbfilterReadNumberHex(pszExpr + 2, ppszExpr, pu16Val);
+ }
+ if (RT_C_IS_ODIGIT(pszExpr[1]))
+ return usbfilterReadNumberDecimal(pszExpr + 1, 8, ppszExpr, pu16Val);
+ /* Solitary 0! */
+ if (RT_C_IS_DIGIT(pszExpr[1]))
+ return VERR_NO_DIGITS;
+ }
+ return usbfilterReadNumberDecimal(pszExpr, 10, ppszExpr, pu16Val);
+}
+
+
+/**
+ * Validates a numeric expression.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS if valid.
+ * @retval VERR_INVALID_PARAMETER if invalid.
+ * @retval VERR_NO_DIGITS if some expression is short of digits.
+ *
+ * @param pszExpr The numeric expression.
+ */
+static int usbfilterValidateNumExpression(const char *pszExpr)
+{
+ /*
+ * An empty expression is fine.
+ */
+ if (!*pszExpr)
+ return VINF_SUCCESS;
+
+ /*
+ * The string format is: "int:((<m>)|([<m>]-[<n>]))(,(<m>)|([<m>]-[<n>]))*"
+ * where <m> and <n> are numbers in decimal, hex (0xNNN) or octal (0NNN).
+ * Spaces are allowed around <m> and <n>.
+ */
+ unsigned cSubExpressions = 0;
+ while (*pszExpr)
+ {
+ if (!strncmp(pszExpr, RT_STR_TUPLE("int:")))
+ pszExpr += strlen("int:");
+
+ /*
+ * Skip remnants of the previous expression and any empty expressions.
+ * ('|' is the expression separator.)
+ */
+ while (*pszExpr == '|' || RT_C_IS_BLANK(*pszExpr) || *pszExpr == '(' || *pszExpr == ')')
+ pszExpr++;
+ if (!*pszExpr)
+ break;
+
+ /*
+ * Parse the expression.
+ */
+ int rc;
+ uint16_t u16First = 0;
+ uint16_t u16Last = 0;
+ if (*pszExpr == '-')
+ {
+ /* -N */
+ pszExpr++;
+ rc = usbfilterReadNumber(&pszExpr, &u16Last);
+ }
+ else
+ {
+ /* M or M,N or M-N or M- */
+ rc = usbfilterReadNumber(&pszExpr, &u16First);
+ if (RT_SUCCESS(rc))
+ {
+ pszExpr = usbfilterSkipBlanks(pszExpr);
+ if (*pszExpr == '-')
+ {
+ pszExpr++;
+ if (*pszExpr) /* M-N */
+ rc = usbfilterReadNumber(&pszExpr, &u16Last);
+ else /* M- */
+ u16Last = UINT16_MAX;
+ }
+ else if (*pszExpr == ',')
+ {
+ /* M,N */
+ pszExpr++;
+ rc = usbfilterReadNumber(&pszExpr, &u16Last);
+ }
+ else
+ {
+ /* M */
+ u16Last = u16First;
+ }
+ }
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * We should either be at the end of the string, at an expression separator (|),
+ * or at the end of an interval filter (')').
+ */
+ if (*pszExpr && *pszExpr != '|' && *pszExpr != ')')
+ return VERR_INVALID_PARAMETER;
+
+ cSubExpressions++;
+ }
+
+ return cSubExpressions ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+}
+
+
+/**
+ * Validates a string pattern.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS if valid.
+ * @retval VERR_INVALID_PARAMETER if invalid.
+ *
+ * @param psz The string pattern.
+ */
+static int usbfilterValidateStringPattern(const char *psz)
+{
+ /*
+ * This is only becomes important if we start doing
+ * sets ([0-9]) and such like.
+ */
+ RT_NOREF1(psz);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Thoroughly validates the USB Filter.
+ *
+ * @returns Appropriate VBox status code.
+ * @param pFilter The filter to validate.
+ */
+USBLIB_DECL(int) USBFilterValidate(PCUSBFILTER pFilter)
+{
+ if (!RT_VALID_PTR(pFilter))
+ return VERR_INVALID_POINTER;
+
+ if (pFilter->u32Magic != USBFILTER_MAGIC)
+ return VERR_INVALID_MAGIC;
+
+ if ( pFilter->enmType <= USBFILTERTYPE_INVALID
+ || pFilter->enmType >= USBFILTERTYPE_END)
+ {
+ Log(("USBFilter: %p - enmType=%d!\n", pFilter, pFilter->enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (pFilter->offCurEnd >= sizeof(pFilter->achStrTab))
+ {
+ Log(("USBFilter: %p - offCurEnd=%#x!\n", pFilter, pFilter->offCurEnd));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Validate that string value offsets are inside the string table. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pFilter->aFields); i++)
+ {
+ if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch)
+ && pFilter->aFields[i].u16Value > pFilter->offCurEnd)
+ {
+ Log(("USBFilter: %p - bad offset=%#x\n", pFilter, pFilter->aFields[i].u16Value));
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ /*
+ * Validate the string table.
+ */
+ if (pFilter->achStrTab[0])
+ {
+ Log(("USBFilter: %p - bad null string\n", pFilter));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ const char *psz = &pFilter->achStrTab[1];
+ while (psz < &pFilter->achStrTab[pFilter->offCurEnd])
+ {
+ const char *pszEnd = RTStrEnd(psz, &pFilter->achStrTab[sizeof(pFilter->achStrTab)] - psz);
+ if (!pszEnd)
+ {
+ Log(("USBFilter: %p - string at %#x isn't terminated!\n",
+ pFilter, psz - &pFilter->achStrTab[0]));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ uint16_t off = (uint16_t)(uintptr_t)(psz - &pFilter->achStrTab[0]);
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(pFilter->aFields); i++)
+ if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch)
+ && pFilter->aFields[i].u16Value == off)
+ break;
+ if (i >= RT_ELEMENTS(pFilter->aFields))
+ {
+ Log(("USBFilter: %p - string at %#x isn't used by anyone! (%s)\n",
+ pFilter, psz - &pFilter->achStrTab[0], psz));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ psz = pszEnd + 1;
+ }
+
+ if ((uintptr_t)(psz - &pFilter->achStrTab[0] - 1) != pFilter->offCurEnd)
+ {
+ Log(("USBFilter: %p - offCurEnd=%#x currently at %#x\n",
+ pFilter, pFilter->offCurEnd, psz - &pFilter->achStrTab[0] - 1));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ while (psz < &pFilter->achStrTab[sizeof(pFilter->achStrTab)])
+ {
+ if (*psz)
+ {
+ Log(("USBFilter: %p - str tab isn't zero padded! %#x: %c\n",
+ pFilter, psz - &pFilter->achStrTab[0], *psz));
+ return VERR_INVALID_PARAMETER;
+ }
+ psz++;
+ }
+
+
+ /*
+ * Validate the fields.
+ */
+ int rc;
+ for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++)
+ {
+ switch (pFilter->aFields[i].enmMatch)
+ {
+ case USBFILTERMATCH_IGNORE:
+ case USBFILTERMATCH_PRESENT:
+ if (pFilter->aFields[i].u16Value)
+ {
+ Log(("USBFilter: %p - #%d/%d u16Value=%d expected 0!\n",
+ pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value));
+ return VERR_INVALID_PARAMETER;
+ }
+ break;
+
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ if (!USBFilterIsNumericField((USBFILTERIDX)i))
+ {
+ Log(("USBFilter: %p - #%d / %d - not numeric field\n",
+ pFilter, i, pFilter->aFields[i].enmMatch));
+ return VERR_INVALID_PARAMETER;
+ }
+ break;
+
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ if (!USBFilterIsNumericField((USBFILTERIDX)i))
+ {
+ Log(("USBFilter: %p - #%d / %d - not numeric field\n",
+ pFilter, i, pFilter->aFields[i].enmMatch));
+ return VERR_INVALID_PARAMETER;
+ }
+ if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd
+ && pFilter->offCurEnd)
+ {
+ Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n",
+ pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd));
+ return VERR_INVALID_PARAMETER;
+ }
+ psz = &pFilter->achStrTab[pFilter->aFields[i].u16Value];
+ rc = usbfilterValidateNumExpression(psz);
+ if (RT_FAILURE(rc))
+ {
+ Log(("USBFilter: %p - #%d / %d - bad num expr: %s (rc=%Rrc)\n",
+ pFilter, i, pFilter->aFields[i].enmMatch, psz, rc));
+ return rc;
+ }
+ break;
+
+ case USBFILTERMATCH_STR_EXACT:
+ case USBFILTERMATCH_STR_EXACT_NP:
+ if (!USBFilterIsStringField((USBFILTERIDX)i))
+ {
+ Log(("USBFilter: %p - #%d / %d - not string field\n",
+ pFilter, i, pFilter->aFields[i].enmMatch));
+ return VERR_INVALID_PARAMETER;
+ }
+ if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd
+ && pFilter->offCurEnd)
+ {
+ Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n",
+ pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd));
+ return VERR_INVALID_PARAMETER;
+ }
+ break;
+
+ case USBFILTERMATCH_STR_PATTERN:
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ if (!USBFilterIsStringField((USBFILTERIDX)i))
+ {
+ Log(("USBFilter: %p - #%d / %d - not string field\n",
+ pFilter, i, pFilter->aFields[i].enmMatch));
+ return VERR_INVALID_PARAMETER;
+ }
+ if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd
+ && pFilter->offCurEnd)
+ {
+ Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n",
+ pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd));
+ return VERR_INVALID_PARAMETER;
+ }
+ psz = &pFilter->achStrTab[pFilter->aFields[i].u16Value];
+ rc = usbfilterValidateStringPattern(psz);
+ if (RT_FAILURE(rc))
+ {
+ Log(("USBFilter: %p - #%d / %d - bad string pattern: %s (rc=%Rrc)\n",
+ pFilter, i, pFilter->aFields[i].enmMatch, psz, rc));
+ return rc;
+ }
+ break;
+
+ default:
+ Log(("USBFilter: %p - #%d enmMatch=%d!\n", pFilter, i, pFilter->aFields[i].enmMatch));
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Find the specified field in the string table.
+ *
+ * @returns Pointer to the string in the string table on success.
+ * NULL if the field is invalid or it doesn't have a string value.
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ */
+DECLINLINE(const char *) usbfilterGetString(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ if ((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END)
+ {
+ switch (pFilter->aFields[enmFieldIdx].enmMatch)
+ {
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ case USBFILTERMATCH_STR_EXACT:
+ case USBFILTERMATCH_STR_EXACT_NP:
+ case USBFILTERMATCH_STR_PATTERN:
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ Assert(pFilter->aFields[enmFieldIdx].u16Value < sizeof(pFilter->achStrTab));
+ return &pFilter->achStrTab[pFilter->aFields[enmFieldIdx].u16Value];
+
+ default:
+ AssertMsgFailed(("%d\n", pFilter->aFields[enmFieldIdx].enmMatch));
+ case USBFILTERMATCH_IGNORE:
+ case USBFILTERMATCH_PRESENT:
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ break;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Gets a number value of a field.
+ *
+ * The field must contain a numeric value.
+ *
+ * @returns The field value on success, -1 on failure (invalid input / not numeric).
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ */
+DECLINLINE(int) usbfilterGetNum(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ if ((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END)
+ {
+ switch (pFilter->aFields[enmFieldIdx].enmMatch)
+ {
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ return pFilter->aFields[enmFieldIdx].u16Value;
+
+ default:
+ AssertMsgFailed(("%d\n", pFilter->aFields[enmFieldIdx].enmMatch));
+ case USBFILTERMATCH_IGNORE:
+ case USBFILTERMATCH_PRESENT:
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ case USBFILTERMATCH_STR_EXACT:
+ case USBFILTERMATCH_STR_EXACT_NP:
+ case USBFILTERMATCH_STR_PATTERN:
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ break;
+ }
+ }
+ return -1;
+}
+
+
+/**
+ * Performs simple pattern matching.
+ *
+ * @returns true on match and false on mismatch.
+ * @param pszExpr The numeric expression.
+ * @param u16Value The value to match.
+ */
+static bool usbfilterMatchNumExpression(const char *pszExpr, uint16_t u16Value)
+{
+ /*
+ * The string format is: "int:((<m>)|([<m>]-[<n>]))(,(<m>)|([<m>]-[<n>]))*"
+ * where <m> and <n> are numbers in decimal, hex (0xNNN) or octal (0NNN).
+ * Spaces are allowed around <m> and <n>.
+ */
+ while (*pszExpr)
+ {
+ if (!strncmp(pszExpr, RT_STR_TUPLE("int:")))
+ pszExpr += strlen("int:");
+
+ /*
+ * Skip remnants of the previous expression and any empty expressions.
+ * ('|' is the expression separator.)
+ */
+ while (*pszExpr == '|' || RT_C_IS_BLANK(*pszExpr) || *pszExpr == '(' || *pszExpr == ')')
+ pszExpr++;
+ if (!*pszExpr)
+ break;
+
+ /*
+ * Parse the expression.
+ */
+ int rc;
+ uint16_t u16First = 0;
+ uint16_t u16Last = 0;
+ if (*pszExpr == '-')
+ {
+ /* -N */
+ pszExpr++;
+ rc = usbfilterReadNumber(&pszExpr, &u16Last);
+ }
+ else
+ {
+ /* M or M,N or M-N or M- */
+ rc = usbfilterReadNumber(&pszExpr, &u16First);
+ if (RT_SUCCESS(rc))
+ {
+ pszExpr = usbfilterSkipBlanks(pszExpr);
+ if (*pszExpr == '-')
+ {
+ pszExpr++;
+ if (*pszExpr) /* M-N */
+ rc = usbfilterReadNumber(&pszExpr, &u16Last);
+ else /* M- */
+ u16Last = UINT16_MAX;
+ }
+ else if (*pszExpr == ',')
+ {
+ /* M,N */
+ pszExpr++;
+ rc = usbfilterReadNumber(&pszExpr, &u16Last);
+ }
+ else
+ {
+ /* M */
+ u16Last = u16First;
+ }
+ }
+ }
+
+ /* On success, we should either be at the end of the string, at an expression
+ * separator (|), or at the end of an interval filter (')').
+ */
+ if (RT_SUCCESS(rc) && *pszExpr && *pszExpr != '|' && *pszExpr != ')')
+ rc = VERR_INVALID_PARAMETER;
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Swap the values if the order is mixed up.
+ */
+ if (u16First > u16Last)
+ {
+ uint16_t u16Tmp = u16First;
+ u16First = u16Last;
+ u16Last = u16Tmp;
+ }
+
+ /*
+ * Perform the compare.
+ */
+ if ( u16Value >= u16First
+ && u16Value <= u16Last)
+ return true;
+ }
+ else
+ {
+ /*
+ * Skip the bad expression.
+ * ('|' is the expression separator.)
+ */
+ while (*pszExpr && *pszExpr != '|')
+ pszExpr++;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Performs simple pattern matching.
+ *
+ * @returns true on match and false on mismatch.
+ * @param pszPattern The pattern to match against.
+ * @param psz The string to match.
+ */
+static bool usbfilterMatchStringPattern(const char *pszPattern, const char *psz)
+{
+ char ch;
+ while ((ch = *pszPattern++))
+ {
+ if (ch == '?')
+ {
+ /*
+ * Matches one char or end of string.
+ */
+ if (*psz)
+ psz++;
+ }
+ else if (ch == '*')
+ {
+ /*
+ * Matches zero or more characters.
+ */
+ /* skip subsequent wildcards */
+ while ( (ch = *pszPattern) == '*'
+ || ch == '?')
+ pszPattern++;
+ if (!ch)
+ /* Pattern ends with a '*' and thus matches the rest of psz. */
+ return true;
+
+ /* Find the length of the following exact pattern sequence. */
+ ssize_t cchMatch = 1;
+ while ( (ch = pszPattern[cchMatch]) != '\0'
+ && ch != '*'
+ && ch != '?')
+ cchMatch++;
+
+ /* Check if the exact pattern sequence is too long. */
+ ssize_t cch = strlen(psz);
+ cch -= cchMatch;
+ if (cch < 0)
+ return false;
+
+ /* Is the rest an exact match? */
+ if (!ch)
+ return memcmp(psz + cch, pszPattern, cchMatch) == 0;
+
+ /*
+ * This is where things normally starts to get recursive or ugly.
+ *
+ * Just to make life simple, we'll skip the nasty stuff and say
+ * that we will do a maximal wildcard match and forget about any
+ * alternative matches.
+ *
+ * If somebody is bored out of their mind one day, feel free to
+ * implement correct matching without using recursion.
+ */
+ ch = *pszPattern;
+ const char *pszMatch = NULL;
+ while ( cch-- >= 0
+ && *psz)
+ {
+ if ( *psz == ch
+ && !strncmp(psz, pszPattern, cchMatch))
+ pszMatch = psz;
+ psz++;
+ }
+ if (!pszMatch)
+ return false;
+
+ /* advance */
+ psz = pszMatch + cchMatch;
+ pszPattern += cchMatch;
+ }
+ else
+ {
+ /* exact match */
+ if (ch != *psz)
+ return false;
+ psz++;
+ }
+ }
+
+ return *psz == '\0';
+}
+
+
+/**
+ * Match a filter against a device.
+ *
+ * @returns true if they match, false if not.
+ *
+ * @param pFilter The filter to match with.
+ * @param pDevice The device data. This is a filter (type ignored) that
+ * contains 'exact' values for all present fields and 'ignore'
+ * values for the non-present fields.
+ *
+ * @remark Both the filter and the device are ASSUMED to be valid because
+ * we don't wish to waste any time in this function.
+ */
+USBLIB_DECL(bool) USBFilterMatch(PCUSBFILTER pFilter, PCUSBFILTER pDevice)
+{
+ return USBFilterMatchRated(pFilter, pDevice) > 0;
+}
+
+
+#if 0 /*def IN_RING0*/ /** @todo convert to proper logging. */
+extern "C" int printf(const char *format, ...);
+# define dprintf(a) printf a
+#else
+# define dprintf(a) do {} while (0)
+#endif
+
+/**
+ * Match a filter against a device and rate the result.
+ *
+ * @returns -1 if no match, matching rate between 1 and 100 (inclusive) if matched.
+ *
+ * @param pFilter The filter to match with.
+ * @param pDevice The device data. This is a filter (type ignored) that
+ * contains 'exact' values for all present fields and 'ignore'
+ * values for the non-present fields.
+ *
+ * @remark Both the filter and the device are ASSUMED to be valid because
+ * we don't wish to waste any time in this function.
+ */
+USBLIB_DECL(int) USBFilterMatchRated(PCUSBFILTER pFilter, PCUSBFILTER pDevice)
+{
+ unsigned iRate = 0;
+dprintf(("USBFilterMatchRated: %p %p\n", pFilter, pDevice));
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++)
+ {
+ switch (pFilter->aFields[i].enmMatch)
+ {
+ case USBFILTERMATCH_IGNORE:
+ iRate += 2;
+ break;
+
+ case USBFILTERMATCH_PRESENT:
+ if (pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE)
+ {
+dprintf(("filter match[%d]: !present\n", i));
+ return -1;
+ }
+ iRate += 2;
+ break;
+
+ case USBFILTERMATCH_NUM_EXACT:
+ if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE
+ || pFilter->aFields[i].u16Value != pDevice->aFields[i].u16Value)
+ {
+if (pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE)
+ dprintf(("filter match[%d]: !num_exact device=ignore\n", i));
+else
+ dprintf(("filter match[%d]: !num_exact %#x (filter) != %#x (device)\n", i, pFilter->aFields[i].u16Value, pDevice->aFields[i].u16Value));
+ return -1;
+ }
+ iRate += 2;
+ break;
+
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE
+ && pFilter->aFields[i].u16Value != pDevice->aFields[i].u16Value)
+ {
+dprintf(("filter match[%d]: !num_exact_np %#x (filter) != %#x (device)\n", i, pFilter->aFields[i].u16Value, pDevice->aFields[i].u16Value));
+ return -1;
+ }
+ iRate += 2;
+ break;
+
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE
+ || !usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i),
+ pDevice->aFields[i].u16Value))
+ {
+dprintf(("filter match[%d]: !num_expression\n", i));
+ return -1;
+ }
+ iRate += 1;
+ break;
+
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE
+ && !usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i),
+ pDevice->aFields[i].u16Value))
+ {
+dprintf(("filter match[%d]: !num_expression_no\n", i));
+ return -1;
+ }
+ iRate += 1;
+ break;
+
+ case USBFILTERMATCH_STR_EXACT:
+ if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE
+ || strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i),
+ usbfilterGetString(pDevice, (USBFILTERIDX)i)))
+ {
+dprintf(("filter match[%d]: !str_exact\n", i));
+ return -1;
+ }
+ iRate += 2;
+ break;
+
+ case USBFILTERMATCH_STR_EXACT_NP:
+ if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE
+ && strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i),
+ usbfilterGetString(pDevice, (USBFILTERIDX)i)))
+ {
+dprintf(("filter match[%d]: !str_exact_np\n", i));
+ return -1;
+ }
+ iRate += 2;
+ break;
+
+ case USBFILTERMATCH_STR_PATTERN:
+ if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE
+ || !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i),
+ usbfilterGetString(pDevice, (USBFILTERIDX)i)))
+ {
+dprintf(("filter match[%d]: !str_pattern\n", i));
+ return -1;
+ }
+ iRate += 1;
+ break;
+
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE
+ && !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i),
+ usbfilterGetString(pDevice, (USBFILTERIDX)i)))
+ {
+dprintf(("filter match[%d]: !str_pattern_np\n", i));
+ return -1;
+ }
+ iRate += 1;
+ break;
+
+ default:
+ AssertMsgFailed(("#%d: %d\n", i, pFilter->aFields[i].enmMatch));
+ return -1;
+ }
+ }
+
+ /* iRate is the range 0..2*cFields - recalc to percent. */
+dprintf(("filter match: iRate=%d", iRate));
+ return iRate == 2 * RT_ELEMENTS(pFilter->aFields)
+ ? 100
+ : (iRate * 100) / (2 * RT_ELEMENTS(pFilter->aFields));
+}
+
+
+/**
+ * Match a filter against a USBDEVICE.
+ *
+ * @returns true if they match, false if not.
+ *
+ * @param pFilter The filter to match with.
+ * @param pDevice The device to match.
+ *
+ * @remark Both the filter and the device are ASSUMED to be valid because
+ * we don't wish to waste any time in this function.
+ */
+USBLIB_DECL(bool) USBFilterMatchDevice(PCUSBFILTER pFilter, PUSBDEVICE pDevice)
+{
+ for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++)
+ {
+ switch (pFilter->aFields[i].enmMatch)
+ {
+ case USBFILTERMATCH_IGNORE:
+ break;
+
+ case USBFILTERMATCH_PRESENT:
+ {
+ const char *psz;
+ switch (i)
+ {
+ case USBFILTERIDX_MANUFACTURER_STR: psz = pDevice->pszManufacturer; break;
+ case USBFILTERIDX_PRODUCT_STR: psz = pDevice->pszProduct; break;
+ case USBFILTERIDX_SERIAL_NUMBER_STR: psz = pDevice->pszSerialNumber; break;
+ default: psz = ""; break;
+ }
+ if (!psz)
+ return false;
+ break;
+ }
+
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ {
+ uint16_t u16Value;
+ switch (i)
+ {
+ case USBFILTERIDX_VENDOR_ID: u16Value = pDevice->idVendor; break;
+ case USBFILTERIDX_PRODUCT_ID: u16Value = pDevice->idProduct; break;
+ case USBFILTERIDX_DEVICE: u16Value = pDevice->bcdDevice; break;
+ case USBFILTERIDX_DEVICE_CLASS: u16Value = pDevice->bDeviceClass; break;
+ case USBFILTERIDX_DEVICE_SUB_CLASS: u16Value = pDevice->bDeviceSubClass; break;
+ case USBFILTERIDX_DEVICE_PROTOCOL: u16Value = pDevice->bDeviceProtocol; break;
+ case USBFILTERIDX_BUS: u16Value = pDevice->bBus; break;
+ case USBFILTERIDX_PORT: u16Value = pDevice->bPort; break;
+ default: u16Value = UINT16_MAX; break;
+
+ }
+ switch (pFilter->aFields[i].enmMatch)
+ {
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ if (pFilter->aFields[i].u16Value != u16Value)
+ return false;
+ break;
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ if (!usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i), u16Value))
+ return false;
+ break;
+ }
+ break;
+ }
+
+ case USBFILTERMATCH_STR_EXACT:
+ case USBFILTERMATCH_STR_EXACT_NP:
+ case USBFILTERMATCH_STR_PATTERN:
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ {
+ const char *psz;
+ switch (i)
+ {
+ case USBFILTERIDX_MANUFACTURER_STR: psz = pDevice->pszManufacturer; break;
+ case USBFILTERIDX_PRODUCT_STR: psz = pDevice->pszProduct; break;
+ case USBFILTERIDX_SERIAL_NUMBER_STR: psz = pDevice->pszSerialNumber; break;
+ default: psz = NULL; break;
+ }
+ switch (pFilter->aFields[i].enmMatch)
+ {
+ case USBFILTERMATCH_STR_EXACT:
+ if ( !psz
+ || strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz))
+ return false;
+ break;
+
+ case USBFILTERMATCH_STR_EXACT_NP:
+ if ( psz
+ && strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz))
+ return false;
+ break;
+
+ case USBFILTERMATCH_STR_PATTERN:
+ if ( !psz
+ || !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz))
+ return false;
+ break;
+
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ if ( psz
+ && !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz))
+ return false;
+ break;
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("#%d: %d\n", i, pFilter->aFields[i].enmMatch));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Checks if the two filters are identical.
+ *
+ * @returns true if the are identical, false if they aren't.
+ * @param pFilter The first filter.
+ * @param pFilter2 The second filter.
+ */
+USBLIB_DECL(bool) USBFilterIsIdentical(PCUSBFILTER pFilter, PCUSBFILTER pFilter2)
+{
+ /* Lazy works here because we're darn strict with zero padding and such elsewhere. */
+ return memcmp(pFilter, pFilter2, sizeof(*pFilter)) == 0;
+}
+
+
+
+/**
+ * Sets the filter type.
+ *
+ * @returns VBox status code.
+ * @retval VERR_INVALID_PARAMETER if the filter type is invalid.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmType The new filter type.
+ */
+USBLIB_DECL(int) USBFilterSetFilterType(PUSBFILTER pFilter, USBFILTERTYPE enmType)
+{
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(enmType > USBFILTERTYPE_INVALID && enmType < USBFILTERTYPE_END, VERR_INVALID_PARAMETER);
+
+ pFilter->enmType = enmType;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Replaces the string value of a field.
+ *
+ * This will remove any existing string value current held by the field from the
+ * string table and then attempt to add the new value. This function can be used
+ * to delete any assigned string before changing the type to numeric by passing
+ * in an empty string. This works because the first byte in the string table is
+ * reserved for the empty (NULL) string.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_BUFFER_OVERFLOW if the string table is full.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid.
+ * @retval VERR_INVALID_POINTER if pszString isn't valid.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ * @param pszString The string to add.
+ * @param fPurge Purge invalid UTF-8 encoding and control characters
+ * before setting it.
+ */
+static int usbfilterSetString(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszString, bool fPurge)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszString, VERR_INVALID_POINTER);
+
+ Assert(pFilter->offCurEnd < sizeof(pFilter->achStrTab));
+ Assert(pFilter->achStrTab[pFilter->offCurEnd] == '\0');
+
+ /*
+ * Remove old string value if any.
+ */
+ if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch)
+ && pFilter->aFields[enmFieldIdx].u16Value != 0)
+ {
+ uint32_t off = pFilter->aFields[enmFieldIdx].u16Value;
+ pFilter->aFields[enmFieldIdx].u16Value = 0; /* Assign it to the NULL string. */
+
+ unsigned cchShift = (unsigned)strlen(&pFilter->achStrTab[off]) + 1;
+ ssize_t cchToMove = (pFilter->offCurEnd + 1) - (off + cchShift);
+ Assert(cchToMove >= 0);
+ if (cchToMove > 0)
+ {
+ /* We're not last - must shift the strings. */
+ memmove(&pFilter->achStrTab[off], &pFilter->achStrTab[off + cchShift], cchToMove);
+ for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++)
+ if ( pFilter->aFields[i].u16Value >= off
+ && USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch))
+ pFilter->aFields[i].u16Value -= cchShift;
+ }
+ pFilter->offCurEnd -= cchShift;
+ Assert(pFilter->offCurEnd < sizeof(pFilter->achStrTab));
+ Assert(pFilter->offCurEnd + cchShift <= sizeof(pFilter->achStrTab));
+
+ /* zero the unused string table (to allow lazyness/strictness elsewhere). */
+ memset(&pFilter->achStrTab[pFilter->offCurEnd], '\0', cchShift);
+ }
+
+ /*
+ * Make a special case for the empty string.
+ * (This also makes the delete logical above work correctly for the last string.)
+ */
+ if (!*pszString)
+ pFilter->aFields[enmFieldIdx].u16Value = 0;
+ else
+ {
+ size_t cch = strlen(pszString);
+ if (pFilter->offCurEnd + cch + 2 > sizeof(pFilter->achStrTab))
+ return VERR_BUFFER_OVERFLOW;
+
+ pFilter->aFields[enmFieldIdx].u16Value = pFilter->offCurEnd + 1;
+ memcpy(&pFilter->achStrTab[pFilter->offCurEnd + 1], pszString, cch + 1);
+ if (fPurge)
+ cch = USBLibPurgeEncoding(&pFilter->achStrTab[pFilter->offCurEnd + 1]);
+ pFilter->offCurEnd += (uint32_t)cch + 1;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Wrapper around usbfilterSetString() that deletes any string value
+ * currently assigned to a field.
+ *
+ * Upon successful return the field contains a null string, nothing or a number.
+ *
+ * This function will validate the field index if there isn't any string
+ * value to delete, thus preventing any extra validating of the index.
+ *
+ * @returns VBox status code. See usbfilterSetString.
+ * @param pFilter The filter.
+ * @param enmFieldIdx The index of the field which string value should be deleted.
+ */
+static int usbfilterDeleteAnyStringValue(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ int rc = VINF_SUCCESS;
+ if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch)
+ && pFilter->aFields[enmFieldIdx].u16Value != 0)
+ rc = usbfilterSetString(pFilter, enmFieldIdx, "", false /*fPurge*/);
+ else if ((unsigned)enmFieldIdx >= (unsigned)USBFILTERIDX_END)
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+}
+
+
+/**
+ * Sets a field to always match (ignore whatever is thrown at it).
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index. This must be a string field.
+ */
+USBLIB_DECL(int) USBFilterSetIgnore(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ int rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx);
+ if (RT_SUCCESS(rc))
+ {
+ pFilter->aFields[enmFieldIdx].enmMatch = USBFILTERMATCH_IGNORE;
+ pFilter->aFields[enmFieldIdx].u16Value = 0;
+ }
+ return rc;
+}
+
+
+/**
+ * Sets a field to match on device field present only.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index. This must be a string field.
+ */
+USBLIB_DECL(int) USBFilterSetPresentOnly(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ int rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx);
+ if (RT_SUCCESS(rc))
+ {
+ pFilter->aFields[enmFieldIdx].enmMatch = USBFILTERMATCH_PRESENT;
+ pFilter->aFields[enmFieldIdx].u16Value = 0;
+ }
+ return rc;
+}
+
+
+/**
+ * Sets a field to exactly match a number.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index. This must be a string field.
+ * @param u16Value The string pattern.
+ * @param fMustBePresent If set, a non-present field on the device will result in a mismatch.
+ * If clear, a non-present field on the device will match.
+ */
+USBLIB_DECL(int) USBFilterSetNumExact(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, uint16_t u16Value, bool fMustBePresent)
+{
+ int rc = USBFilterIsNumericField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+ if (RT_SUCCESS(rc))
+ {
+ rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx);
+ if (RT_SUCCESS(rc))
+ {
+ pFilter->aFields[enmFieldIdx].u16Value = u16Value;
+ pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_NUM_EXACT : USBFILTERMATCH_NUM_EXACT_NP;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Sets a field to match a numeric expression.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_BUFFER_OVERFLOW if the string table is full.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or the numeric expression aren't valid.
+ * @retval VERR_INVALID_POINTER if pszExpression isn't a valid pointer.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index. This must be a string field.
+ * @param pszExpression The numeric expression.
+ * @param fMustBePresent If set, a non-present field on the device will result in a mismatch.
+ * If clear, a non-present field on the device will match.
+ */
+USBLIB_DECL(int) USBFilterSetNumExpression(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszExpression, bool fMustBePresent)
+{
+ int rc = USBFilterIsNumericField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+ if (RT_SUCCESS(rc))
+ {
+ /* Strip leading spaces and empty sub expressions (||). */
+ while (*pszExpression && (RT_C_IS_BLANK(*pszExpression) || *pszExpression == '|'))
+ pszExpression++;
+
+ rc = usbfilterValidateNumExpression(pszExpression);
+ if (RT_SUCCESS(rc))
+ {
+ /* We could optimize the expression further (stripping spaces, convert numbers),
+ but it's more work than what it's worth and it could upset some users. */
+ rc = usbfilterSetString(pFilter, enmFieldIdx, pszExpression, false /*fPurge*/);
+ if (RT_SUCCESS(rc))
+ pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_NUM_EXPRESSION : USBFILTERMATCH_NUM_EXPRESSION_NP;
+ else if (rc == VERR_NO_DIGITS)
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Sets a field to exactly match a string.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_BUFFER_OVERFLOW if the string table is full.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid.
+ * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index. This must be a string field.
+ * @param pszValue The string value.
+ * @param fMustBePresent If set, a non-present field on the device will result in a mismatch.
+ * If clear, a non-present field on the device will match.
+ * @param fPurge Purge invalid UTF-8 encoding and control
+ * characters before setting it.
+ */
+USBLIB_DECL(int) USBFilterSetStringExact(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszValue,
+ bool fMustBePresent, bool fPurge)
+{
+ int rc = USBFilterIsStringField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+ if (RT_SUCCESS(rc))
+ {
+ rc = usbfilterSetString(pFilter, enmFieldIdx, pszValue, fPurge);
+ if (RT_SUCCESS(rc))
+ pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_STR_EXACT : USBFILTERMATCH_STR_EXACT_NP;
+ }
+ return rc;
+}
+
+
+/**
+ * Sets a field to match a string pattern.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_BUFFER_OVERFLOW if the string table is full.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or pattern aren't valid.
+ * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index. This must be a string field.
+ * @param pszPattern The string pattern.
+ * @param fMustBePresent If set, a non-present field on the device will result in a mismatch.
+ * If clear, a non-present field on the device will match.
+ */
+USBLIB_DECL(int) USBFilterSetStringPattern(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszPattern, bool fMustBePresent)
+{
+ int rc = USBFilterIsStringField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+ if (RT_SUCCESS(rc))
+ {
+ rc = usbfilterValidateStringPattern(pszPattern);
+ if (RT_SUCCESS(rc))
+ {
+ rc = usbfilterSetString(pFilter, enmFieldIdx, pszPattern, false /*fPurge*/);
+ if (RT_SUCCESS(rc))
+ pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_STR_PATTERN : USBFILTERMATCH_STR_PATTERN_NP;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Sets the must-be-present part of a field.
+ *
+ * This only works on field which already has matching criteria. This means
+ * that field marked 'ignore' will not be processed and will result in a
+ * warning status code.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VWRN_INVALID_PARAMETER if the field is marked 'ignore'. No assertions.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or pattern aren't valid.
+ * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ * @param fMustBePresent If set, a non-present field on the device will result in a mismatch.
+ * If clear, a non-present field on the device will match.
+ */
+USBLIB_DECL(int) USBFilterSetMustBePresent(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, bool fMustBePresent)
+{
+ AssertPtrReturn(pFilter, VERR_INVALID_POINTER);
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END, VERR_INVALID_PARAMETER);
+
+ USBFILTERMATCH enmMatch = (USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch;
+ if (fMustBePresent)
+ {
+ switch (enmMatch)
+ {
+ case USBFILTERMATCH_IGNORE:
+ return VWRN_INVALID_PARAMETER;
+
+ case USBFILTERMATCH_PRESENT:
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_STR_EXACT:
+ case USBFILTERMATCH_STR_PATTERN:
+ break;
+
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ enmMatch = USBFILTERMATCH_NUM_EXACT;
+ break;
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ enmMatch = USBFILTERMATCH_NUM_EXPRESSION;
+ break;
+ case USBFILTERMATCH_STR_EXACT_NP:
+ enmMatch = USBFILTERMATCH_STR_EXACT;
+ break;
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ enmMatch = USBFILTERMATCH_STR_PATTERN;
+ break;
+ default:
+ AssertMsgFailedReturn(("%p: enmFieldIdx=%d enmMatch=%d\n", pFilter, enmFieldIdx, enmMatch), VERR_INVALID_MAGIC);
+ }
+ }
+ else
+ {
+ switch (enmMatch)
+ {
+ case USBFILTERMATCH_IGNORE:
+ return VWRN_INVALID_PARAMETER;
+
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ case USBFILTERMATCH_STR_EXACT_NP:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ break;
+
+ case USBFILTERMATCH_PRESENT:
+ enmMatch = USBFILTERMATCH_IGNORE;
+ break;
+ case USBFILTERMATCH_NUM_EXACT:
+ enmMatch = USBFILTERMATCH_NUM_EXACT_NP;
+ break;
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ enmMatch = USBFILTERMATCH_NUM_EXPRESSION_NP;
+ break;
+ case USBFILTERMATCH_STR_EXACT:
+ enmMatch = USBFILTERMATCH_STR_EXACT_NP;
+ break;
+ case USBFILTERMATCH_STR_PATTERN:
+ enmMatch = USBFILTERMATCH_STR_PATTERN_NP;
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%p: enmFieldIdx=%d enmMatch=%d\n", pFilter, enmFieldIdx, enmMatch), VERR_INVALID_MAGIC);
+ }
+ }
+
+ pFilter->aFields[enmFieldIdx].enmMatch = enmMatch;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the filter type.
+ *
+ * @returns The filter type.
+ * USBFILTERTYPE_INVALID if the filter is invalid.
+ * @param pFilter The filter.
+ */
+USBLIB_DECL(USBFILTERTYPE) USBFilterGetFilterType(PCUSBFILTER pFilter)
+{
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, USBFILTERTYPE_INVALID);
+ return pFilter->enmType;
+}
+
+
+/**
+ * Gets the matching method for a field.
+ *
+ * @returns The matching method on success, UBFILTERMATCH_INVALID on invalid field index.
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ */
+USBLIB_DECL(USBFILTERMATCH) USBFilterGetMatchingMethod(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ if ( pFilter->u32Magic == USBFILTER_MAGIC
+ && (unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END)
+ return (USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch;
+ return USBFILTERMATCH_INVALID;
+}
+
+
+/**
+ * Gets the numeric value of a field.
+ *
+ * The field must contain a number, we're not doing any conversions for you.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid or if the field doesn't contain a number.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ * @param pu16Value Where to store the value.
+ */
+USBLIB_DECL(int) USBFilterQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, uint16_t *pu16Value)
+{
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC);
+ int iValue = usbfilterGetNum(pFilter, enmFieldIdx);
+ if (iValue == -1)
+ return VERR_INVALID_PARAMETER;
+ *pu16Value = (uint16_t)iValue;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the numeric value of a field.
+ *
+ * The field must contain a number, we're not doing any conversions for you.
+ *
+ * @returns The field value on success, -1 on failure (invalid input / not numeric).
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ */
+USBLIB_DECL(int) USBFilterGetNum(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, -1);
+ return usbfilterGetNum(pFilter, enmFieldIdx);
+}
+
+
+/**
+ * Gets the string value of a field.
+ *
+ * The field must contain a string, we're not doing any conversions for you.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_BUFFER_OVERFLOW if the buffer isn't sufficient to hold the string. The buffer
+ * will be filled with as much of the string that'll fit.
+ * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid or if the field doesn't contain a string.
+ * @retval VERR_INVALID_MAGIC if pFilter is invalid.
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ * @param pszBuf Where to store the string.
+ * @param cchBuf The size of the buffer.
+ */
+USBLIB_DECL(int) USBFilterQueryString(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, char *pszBuf, size_t cchBuf)
+{
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC);
+
+ const char *psz = usbfilterGetString(pFilter, enmFieldIdx);
+ if (RT_UNLIKELY(!psz))
+ return VERR_INVALID_PARAMETER;
+
+ int rc = VINF_SUCCESS;
+ size_t cch = strlen(psz);
+ if (cch < cchBuf)
+ memcpy(pszBuf, psz, cch + 1);
+ else
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ if (cchBuf)
+ {
+ memcpy(pszBuf, psz, cchBuf - 1);
+ pszBuf[cchBuf - 1] = '\0';
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Gets the string table entry for a field.
+ *
+ * @returns Pointer to the string. (readonly!)
+ *
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ */
+USBLIB_DECL(const char *) USBFilterGetString(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, NULL);
+
+ const char *psz = usbfilterGetString(pFilter, enmFieldIdx);
+ if (RT_UNLIKELY(!psz))
+ return NULL;
+ return psz;
+}
+
+
+/**
+ * Gets the string length of a field containing a string.
+ *
+ * @returns String length on success, -1 on failure (not a string, bad filter).
+ * @param pFilter The filter.
+ * @param enmFieldIdx The field index.
+ */
+USBLIB_DECL(ssize_t) USBFilterGetStringLen(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx)
+{
+ if (RT_LIKELY(pFilter->u32Magic == USBFILTER_MAGIC))
+ {
+ const char *psz = usbfilterGetString(pFilter, enmFieldIdx);
+ if (RT_LIKELY(psz))
+ return strlen(psz);
+ }
+ return -1;
+}
+
+
+/**
+ * Check if any of the fields are set to something substatial.
+ *
+ * Consider the fileter a wildcard if this returns false.
+ *
+ * @returns true / false.
+ * @param pFilter The filter.
+ */
+USBLIB_DECL(bool) USBFilterHasAnySubstatialCriteria(PCUSBFILTER pFilter)
+{
+ AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, false);
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++)
+ {
+ switch (pFilter->aFields[i].enmMatch)
+ {
+ case USBFILTERMATCH_IGNORE:
+ case USBFILTERMATCH_PRESENT:
+ break;
+
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ case USBFILTERMATCH_STR_EXACT:
+ case USBFILTERMATCH_STR_EXACT_NP:
+ return true;
+
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ {
+ const char *psz = usbfilterGetString(pFilter, (USBFILTERIDX)i);
+ if (psz)
+ {
+ while (*psz && (*psz == '|' || RT_C_IS_BLANK(*psz)))
+ psz++;
+ if (*psz)
+ return true;
+ }
+ break;
+ }
+
+ case USBFILTERMATCH_STR_PATTERN:
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ {
+ const char *psz = usbfilterGetString(pFilter, (USBFILTERIDX)i);
+ if (psz)
+ {
+ while (*psz && (*psz == '*' || *psz == '?'))
+ psz++;
+ if (*psz)
+ return true;
+ }
+ break;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+
+/**
+ * Checks whether the specified field is a numeric field or not.
+ *
+ * @returns true / false.
+ * @param enmFieldIdx The field index.
+ */
+USBLIB_DECL(bool) USBFilterIsNumericField(USBFILTERIDX enmFieldIdx)
+{
+ switch (enmFieldIdx)
+ {
+ case USBFILTERIDX_VENDOR_ID:
+ case USBFILTERIDX_PRODUCT_ID:
+ case USBFILTERIDX_DEVICE:
+ case USBFILTERIDX_DEVICE_CLASS:
+ case USBFILTERIDX_DEVICE_SUB_CLASS:
+ case USBFILTERIDX_DEVICE_PROTOCOL:
+ case USBFILTERIDX_BUS:
+ case USBFILTERIDX_PORT:
+ return true;
+
+ default:
+ AssertMsgFailed(("%d\n", enmFieldIdx));
+ RT_FALL_THRU();
+ case USBFILTERIDX_MANUFACTURER_STR:
+ case USBFILTERIDX_PRODUCT_STR:
+ case USBFILTERIDX_SERIAL_NUMBER_STR:
+ return false;
+ }
+}
+
+
+/**
+ * Checks whether the specified field is a string field or not.
+ *
+ * @returns true / false.
+ * @param enmFieldIdx The field index.
+ */
+USBLIB_DECL(bool) USBFilterIsStringField(USBFILTERIDX enmFieldIdx)
+{
+ switch (enmFieldIdx)
+ {
+ default:
+ AssertMsgFailed(("%d\n", enmFieldIdx));
+ RT_FALL_THRU();
+ case USBFILTERIDX_VENDOR_ID:
+ case USBFILTERIDX_PRODUCT_ID:
+ case USBFILTERIDX_DEVICE:
+ case USBFILTERIDX_DEVICE_CLASS:
+ case USBFILTERIDX_DEVICE_SUB_CLASS:
+ case USBFILTERIDX_DEVICE_PROTOCOL:
+ case USBFILTERIDX_BUS:
+ case USBFILTERIDX_PORT:
+ return false;
+
+ case USBFILTERIDX_MANUFACTURER_STR:
+ case USBFILTERIDX_PRODUCT_STR:
+ case USBFILTERIDX_SERIAL_NUMBER_STR:
+ return true;
+ }
+}
+
+
+/**
+ * Checks whether the specified matching method uses a numeric value or not.
+ *
+ * @returns true / false.
+ * @param enmMatchingMethod The matching method.
+ */
+USBLIB_DECL(bool) USBFilterIsMethodUsingNumericValue(USBFILTERMATCH enmMatchingMethod)
+{
+ switch (enmMatchingMethod)
+ {
+ default:
+ AssertMsgFailed(("%d\n", enmMatchingMethod));
+ RT_FALL_THRU();
+ case USBFILTERMATCH_IGNORE:
+ case USBFILTERMATCH_PRESENT:
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ case USBFILTERMATCH_STR_EXACT:
+ case USBFILTERMATCH_STR_EXACT_NP:
+ case USBFILTERMATCH_STR_PATTERN:
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ return false;
+
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ return true;
+ }
+}
+
+
+/**
+ * Checks whether the specified matching method uses a string value or not.
+ *
+ * @returns true / false.
+ * @param enmMatchingMethod The matching method.
+ */
+USBLIB_DECL(bool) USBFilterIsMethodUsingStringValue(USBFILTERMATCH enmMatchingMethod)
+{
+ switch (enmMatchingMethod)
+ {
+ default:
+ AssertMsgFailed(("%d\n", enmMatchingMethod));
+ RT_FALL_THRU();
+ case USBFILTERMATCH_IGNORE:
+ case USBFILTERMATCH_PRESENT:
+ case USBFILTERMATCH_NUM_EXACT:
+ case USBFILTERMATCH_NUM_EXACT_NP:
+ return false;
+
+ case USBFILTERMATCH_NUM_EXPRESSION:
+ case USBFILTERMATCH_NUM_EXPRESSION_NP:
+ case USBFILTERMATCH_STR_EXACT:
+ case USBFILTERMATCH_STR_EXACT_NP:
+ case USBFILTERMATCH_STR_PATTERN:
+ case USBFILTERMATCH_STR_PATTERN_NP:
+ return true;
+ }
+}
+
+
+/**
+ * Checks if a matching method is for numeric fields or not.
+ *
+ * @returns true / false.
+ * @param enmMatchingMethod The matching method.
+ */
+USBLIB_DECL(bool) USBFilterIsMethodNumeric(USBFILTERMATCH enmMatchingMethod)
+{
+ return enmMatchingMethod >= USBFILTERMATCH_NUM_FIRST
+ && enmMatchingMethod <= USBFILTERMATCH_NUM_LAST;
+}
+
+/**
+ * Checks if a matching method is for string fields or not.
+ *
+ * @returns true / false.
+ * @param enmMatchingMethod The matching method.
+ */
+USBLIB_DECL(bool) USBFilterIsMethodString(USBFILTERMATCH enmMatchingMethod)
+{
+ return enmMatchingMethod >= USBFILTERMATCH_STR_FIRST
+ && enmMatchingMethod <= USBFILTERMATCH_STR_LAST;
+}
+