Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tosc_matchAddress #12

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions tinyosc.c
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@
#include <netinet/in.h>
#define tosc_strncpy(_dst, _src, _len) strncpy(_dst, _src, _len)
#endif
#if __unix__ && !__APPLE__
#if (__unix__ && !__APPLE__) || (ESP_PLATFORM)
#include <endian.h>
#define htonll(x) htobe64(x)
#define ntohll(x) be64toh(x)
@@ -98,13 +98,13 @@ uint32_t tosc_getLength(tosc_message *o) {

int32_t tosc_getNextInt32(tosc_message *o) {
// convert from big-endian (network btye order)
const int32_t i = (int32_t) ntohl(*((uint32_t *) o->marker));
const int32_t i = (int32_t) ntohl(*((const uint32_t *) o->marker));
o->marker += 4;
return i;
}

int64_t tosc_getNextInt64(tosc_message *o) {
const int64_t i = (int64_t) ntohll(*((uint64_t *) o->marker));
const int64_t i = (int64_t) ntohll(*((const uint64_t *) o->marker));
o->marker += 8;
return i;
}
@@ -115,15 +115,15 @@ uint64_t tosc_getNextTimetag(tosc_message *o) {

float tosc_getNextFloat(tosc_message *o) {
// convert from big-endian (network btye order)
const uint32_t i = ntohl(*((uint32_t *) o->marker));
const uint32_t i = ntohl(*((const uint32_t *) o->marker));
o->marker += 4;
return *((float *) (&i));
return *((const float *) (&i));
}

double tosc_getNextDouble(tosc_message *o) {
const uint64_t i = ntohll(*((uint64_t *) o->marker));
const uint64_t i = ntohll(*((const uint64_t *) o->marker));
o->marker += 8;
return *((double *) (&i));
return *((const double *) (&i));
}

const char *tosc_getNextString(tosc_message *o) {
@@ -200,14 +200,14 @@ static uint32_t tosc_vwrite(char *buffer, const int len,
case 'f': {
if (i + 4 > len) return -3;
const float f = (float) va_arg(ap, double);
*((uint32_t *) (buffer+i)) = htonl(*((uint32_t *) &f));
*((uint32_t *) (buffer+i)) = htonl(*((const uint32_t *) &f));
i += 4;
break;
}
case 'd': {
if (i + 8 > len) return -3;
const double f = (double) va_arg(ap, double);
*((uint64_t *) (buffer+i)) = htonll(*((uint64_t *) &f));
*((uint64_t *) (buffer+i)) = htonll(*((const uint64_t *) &f));
i += 8;
break;
}
@@ -286,8 +286,8 @@ void tosc_printOscBuffer(char *buffer, const int len) {
}

void tosc_printMessage(tosc_message *osc) {
printf("[%i bytes] %s %s",
osc->len, // the number of bytes in the OSC message
printf("[%d bytes] %s %s",
(int)osc->len, // the number of bytes in the OSC message
tosc_getAddress(osc), // the OSC address string, e.g. "/button1"
tosc_getFormat(osc)); // the OSC format string, e.g. "f"

@@ -308,7 +308,7 @@ void tosc_printMessage(tosc_message *osc) {
}
case 'f': printf(" %g", tosc_getNextFloat(osc)); break;
case 'd': printf(" %g", tosc_getNextDouble(osc)); break;
case 'i': printf(" %d", tosc_getNextInt32(osc)); break;
case 'i': printf(" %d", (int)tosc_getNextInt32(osc)); break;
case 'h': printf(" %lld", tosc_getNextInt64(osc)); break;
case 't': printf(" %lld", tosc_getNextTimetag(osc)); break;
case 's': printf(" %s", tosc_getNextString(osc)); break;
659 changes: 659 additions & 0 deletions tinyosc_match.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,659 @@
/*
* tinyosc_match.c
*
* Created on: May 17, 2022
* Author: master
*/

#include <stdio.h>
#include <stdbool.h>

#define _DEBUGBLOCK_ 0

/**
* matching the {foo,bar,yeah} string list scenario
*
* returns true if name s (i.e the current character):
*
* will match AT LEAST ONE of the strings in pattern p {a,b,c}
* s wont match ANY of the strings in pattern p {!a,b,c} (This is a deliberate extension to OSC 1.1 convenient though)
*
* if match is successful, both the entire pattern ({...}) and the matched name portion will be 'consumed', meaning that
* the pointers will have advanced, the length information will be adjusted (decreased by that much too)
* otherwise, the pointers wont be changed
*
* @param p pointer to char pointer for the pattern to match, keeps advancing while matching (starting with '{')
* @param plen pointer to the pattern length, keeps decrementing while matching (including leading '{')
* @param s pointer to char pointer for the name to match, keeps advancing while matching (starting with '{')
* @param slen pointer to the name length, keeps decrementing while matching (including leading '{')
*
* @return true if pattern matches the name, false otherwise
*/
static bool osc_matchStringList(const char** const p, int* const plen, const char** const s, int* const slen)
{
//{foo,bar,bla} contains a ',' separated list of strings, any of them can match

//sanity checks
if(*plen<=0) return false;
if(**p != '{') return false;

#if _DEBUGBLOCK_

const char* _p = *p;
int _plen = *plen;
const char* _s = *s;
int _slen = *slen;

#endif

const char* pp = *p+1; //pass '{'
int pplen = (*plen)-1;

bool inv = false; //!-logic
if(pplen<=0) return false;
if(*pp == '!') { inv = true; --pplen; ++pp; } //check and remember for inverse logic, consume the '!\ if present

if(pplen<=0) return false;
if(*slen<=0) return false;

//from here on we need logic awareness
bool m = inv; //definition of done

__res:
while(pplen>0 && m==inv) //in inverted case, one mismatch doesnt prove anything, we need to scan all parts, also, while no match for the noninverted case
{
//for resets to compare to probable next part
const char* ss = *s;
int sslen = *slen;
bool matched = true; //default for one new round only

while(pplen>0 && m==inv)
switch(*pp)
{
case '}':
case ',':
{
//consumed what needed from p
//at this point, we are either done with successfully comparing the last one or came from an mismatch in any of the strings

if(matched)
{
//match found

m = true;

if(inv) goto __out1; //we matched one string, but shouldn't
else
{
//we matched at least one string, leave gracefully
//will leave due to m(true) != inv(false)

//consume the matching part of the name
*s = ss; *slen = sslen;
}
}
else if(*pp == '}') //and also not matched
{
//no more elements to compare to
m = false;

//nothing matched so far
if(inv)
{
//leave gracefully, we didnt find any matches
//will leave due to m(false) != inv(true)

//consuming the non matching part of the name must not be done here, as it can match additional conditions later
//this can be a problem though,
//example: /{!bt,dt}est --> /atest
//successfully "not matches" bt neither dt, and while the
//"/{!bt,dt}" portion will get consumed in the pattern, resulting in "est"
//the name will remain the same, "atest
//the only solution would be to somehow drop the "at" in the name
//this requires usage of the skipping mechanisms, from * operator
//dropping anything from name until the first match is encountered
//or nothing to drop anymore
//for now, this is to wild and probably totally unnecessary
}
else goto __out1; //if no more elements to scan, leave with error
}
else
{
//no match, and ','so still things to do
goto __res; //this will never come here, just for clarity, is handled in the default case
}
}
break;
default:
{
if(sslen<=0) return false;
if(*pp != *ss)
{
//mismatch
matched = false;

//forward to next string element to compare or skip to '}'
while(pplen>0)
{
if(*pp == ',') { --pplen; ++pp; break; }
else if(*pp == '}') { break; } //no more parts to compare to, nothing found
--pplen; ++pp; //consume mismatching chars
}
if(*pp == '}') break; //we already have encountered the end ("-]" case), don't consume, just re-evaluate
goto __res; //s params will be restored, we are either out of options pointing to '}' or to the next string
}
else
{
//match one char of the string only, we can remain matched=true
//continue string compare by character, until we hit ',' or '}'
}
--pplen; ++pp; --sslen; ++ss; //compare next ch
}
break;
} //switch
} //while

//success

//make sure pp and pplen pass } to rest on next
while(pplen>0) { if(*pp == '}') { break; } --pplen; ++pp; }
if(pplen <= 0) return false; //no '}' found

--pplen; ++pp; //consume the '}'
*p = pp; *plen = pplen;

//consuming the matching string in name has already been done

__out1:

//determine the result
if(!inv && !m) return false; //normal case
if(inv && m) return false; //inverted case

return true;
}

/**
* matching the [a-z] char list scenario
*
* returns true if name s (i.e the current character):
*
* will match AT LEAST ONE of the characters in pattern p [a-z] or [abcDEg] or even [adef-xYu1-4] or [-z] or [a-] (doesn't make much sense though)
* wont match ANY of the characters in pattern p [!a-z] or the respectively negated examples above
*
* if match is successful, both the entire pattern ([...]) and the matched name portion (one char) will be 'consumed', meaning that
* the pointers will have advanced, the length information will be adjusted (decreased by that much too)
* otherwise, the pointers wont be changed
*
* @param p pointer to char pointer for the pattern to match, keeps advancing while matching (starting with '[')
* @param plen pointer to the pattern length, keeps decrementing while matching (including leading '[')
* @param s pointer to char pointer for the name to match, keeps advancing while matching (starting with '[')
* @param slen pointer to the name length, keeps decrementing while matching (including leading '[')
*
* @return true if pattern matches the name, false otherwise
*/
static bool osc_matchCharList(const char** const p, int* const plen, const char** const s, int* const slen)
{
//[abc], [!abc], [a-c], [!a-c] characters match, only one range indication is supported. [a-eC-F] is not possible
//! indicates a negation of logic

//sanity checks
if(*plen<=0) return false;
if(**p != '[') return false;

#if _DEBUGBLOCK_

const char* _p = *p;
int _plen = *plen;
const char* _s = *s;
int _slen = *slen;

#endif

const char* pp = *p+1; //pass '['
int pplen = (*plen)-1;

bool inv = false; //!-logic
if(pplen<=0) return false;
if(*pp == '!') { inv = true; --pplen; ++pp; } //check and remember for inverse logic, consume the '!\ if present

if(pplen<=0) return false;
if(*slen<=0) return false;

//from here on we need logic awareness
bool m = inv; //default invalid value of match depends on expectation
unsigned char cha = 0, chb = 0; //invalid range

//case [abc] or [a-c] is done implicitly, analyzing the others chars
while(pplen>0 && m==inv)
{
switch(*pp)
{
case ']': //consumed what needed from pattern, nothing matched
{
if(!inv) goto __out2; //nothing encountered up until now, this is a no match

//in inverted logic, no error up to end means we haven't found any forbidden match
//success, if no errors found up to now
m = !inv;
}
break;
case '-': //range encountered, could be first after, could be last before ]
{
int offs = inv?2:1;
if (pplen+offs == *plen)
{
//'-' is first after [ or [!
cha = 0; //open range to the left
}
else
{
//range spec found after previous character
//leave the left side of the range to the previously set value
}
//automatically open the range to the far right, it will be corrected if a letter is present after that
chb = 0xFFu;

--pplen; ++pp; //consume '-'

if(pplen <= 0) return false; //no ']' possible
if(*pp !=']') continue; //case "-]" needs to fall through, otherwise restart, we already consumed the '-'

//fall through for no more characters, so we get to check the last range
}
/* no break */
default:
{
if(cha == chb)
{
//no range check, update with single char range for later check
cha = *pp;
chb = *pp;
}
else if(*pp ==']')
{
//range already set when encountered last bracket, from fall through of "-]"
}
else
{
//range opened, update only right side with the current value
chb = *pp;
}

//check for out of range or != test char
if(**s < cha || **s > chb)
{
//out of range or no match
//for normal logic, a miss is not yet an error, so retry with next
//for inverted logic, all possible ranges need to be tested to be successful, continue
}
//check for inside range
else if (**s >= cha && **s <= chb)
{
//in range
if(inv) goto __out2; //match with forbidden range

//success, if no errors found up to now
m = !inv;
}
else
{
//sanity, may never occur, but..
goto __out2;
}

//close the range, so it can be updated with next char
cha = chb;
}
break;
}//switch

if(*pp ==']') continue; //we already have encountered the end ("-]" case), don't consume, just re-evaluate
--pplen; ++pp; //consume test char
} //while next characters


//a match has been found, pass closing bracket ']' consuming all the char list fuzz except ']'
while(pplen>0) { if(*pp == ']') { break; } --pplen; ++pp; }
if(pplen <= 0) return false; //no ']' found

//success

--pplen; ++pp; //consume the ']'
*p = pp; *plen = pplen;

--*slen; ++*s; //consuming the matching character in name

__out2:

//determine the result
if(!inv && !m) return false; //normal case
if(inv && m) return false; //inverted case

return true;
}

//forward
static bool osc_matchPattern0(const char** const p, int* const plen, const char** const s, int* const slen, unsigned int ctx);
static bool osc_matchPatternImpl(const char** const p, int* const plen, const char** const s, int* const slen, unsigned int ctx);

/**
* matching the '*' scenario
*
* we may skip characters in name while still trying to match, up until the respective section end
*
* PATTERN SECTION END REACHED
*
* case 1: "*" (mo further sections)
* A name has no further sections: match
* B name has further sections: no match
* case 2: "* /" (more sections)
* A name has no further sections: no match
* B name has further sections: fully recurse into new section
*
* MORE PATTERN IN SECTION
* case 3: "*cd"
* retry matching, if mismatch skip a name character and retry, otherwise restore normal process
* will eventually end in case 1
*
* case 4: "*cd/"
* retry matching, if mismatch skip a name character and retry, otherwise restore normal process
* will eventually end in case 2
*
* @param p pointer to char pointer for the pattern to match, keeps advancing while matching (starting with '[')
* @param plen pointer to the pattern length, keeps decrementing while matching (including leading '[')
* @param s pointer to char pointer for the name to match, keeps advancing while matching (starting with '[')
* @param slen pointer to the name length, keeps decrementing while matching (including leading '[')
*
* @return true if pattern matches the name, false otherwise
*/
static bool osc_matchStar(const char** const p, int* const plen, const char** const s, int* const slen, unsigned int ctx)
{
//ignore unmatched and retry with a char ahead, a match resets it to normal state

//sanity checks
if(*plen<=0) return false;
if(**p != '*') return false;

const char* pp = *p+1; //pass '*'
int pplen = (*plen)-1;
const char* ss = *s;
int sslen = *slen;

//if pattern section finished
if((*pp == '/') || (pplen<=0))
{
//case 1 and 2

//("*" pr "*/.." case)
//pattern section is finished (or no more sections) right after the '*'
//so anything in name, that might follow up until its own end of section is alright
//so we need to check for next section in name
//but if more sections available...recurse

//skip current name section
while(sslen>0) { if(*ss == '/') { break; } --sslen; ++ss; }

if(sslen<=0)
{
//no section in name
if(pplen<=0)
{
//no more name sections, no more pattern sections, match (CASE 1 A)
*p = pp; *plen = pplen;
*s = ss; *slen = sslen;
return true;
}
else
{
return false;// CASE 2 A
}
}

//still have a section in name

if(pplen<=0) return false; //but no more pattern sections, this is a mismatch even with '*' (CASE 1 B)

//still have a section in pattern

//so recurse there together with the pattern (CASE 2 B)
if(!osc_matchPatternImpl(&pp, &pplen, &ss, &sslen, ctx)) return false;
//consume the matched stuff
*p = pp; *plen = pplen;
*s = ss; *slen = sslen;
return true;
}
else
{
//case 3 and 4
//more in pattern, so we need to match ("*abc" case)
while(1)
{
//entering recursive star mode, saving previous conditions
if(osc_matchPattern0(&pp, &pplen, &ss, &sslen, ctx))
{
//restore normal mode with new conditions
*p = pp; *plen = pplen;
*s = ss; *slen = sslen;
return true; //continue normal evaluation
}
else
{
//match went wrong, try match further in name string
--sslen; ++ss;

if(sslen<=0) return false; //no name left but pattern goes on /?
}
} //while
}
}

/**
* main recursable osc matching function
* matching an osc pattern to an osc name or path, both including leading '/'.
* several sections of /.../.../... are allowed in both.
*
* rule of thumb for the code:
* return false on error, switch break is continue to lookout for next matching conditions
* if all the pattern matched and no more name to match, this is a match, returning true
*
* osc container and osc methods use same logic, no need to differentiate here
*
* this function uses recursion because of the '*' and the "//" operators,
* hence it has to be called by the @osc_matchPatternImpl. It should NOT be called directly, because it skips the sanity checks.
*
* if match is successful, both the matched pattern (including possible [...], {...}, '*', "//" etc) and the matched name portion will be 'consumed', meaning that
* the pointers will have advanced, the length information will be adjusted (decreased by that much too)
* otherwise, the pointers will remain at the last matched position.
*
* @param p pointer to char pointer for the pattern to match, keeps advancing while matching (starting with '/')
* @param plen pointer to the pattern length, keeps decrementing while matching (including leading '/')
* @param s pointer to char pointer for the name to match, keeps advancing while matching (starting with '/')
* @param slen pointer to the name length, keeps decrementing while matching (including leading '/')
*
* @return true if pattern matches the adress, false otherwise
*/
static bool osc_matchPattern0(const char** const p, int* const plen, const char** const s, int* const slen, unsigned int ctx)
{
//no sanity checks, internal function block

int plen_ = *plen; //to detect //

//try get first char of pattern
while(*plen>0)
{
#if _DEBUGBLOCK_

const char* _p = *p;
int _plen = *plen;
const char* _s = *s;
int _slen = *slen;

#endif

//*p is available, consume, don't forget to ++p and --plen after usage, same for s, slen
switch(**p)
{
case '*': if(!osc_matchStar(p, plen, s, slen, ctx)) return false; //star match went wrong
return true; //exception, recursions decide and consume for themselves
case '?':
{
if(*slen<=0) return false; //no name left, but pattern left
//matches any single char //TEST
--*plen; ++*p; --*slen; ++*s; //consume
//continue
}
break;

case '{': if(!osc_matchStringList(p, plen, s, slen)) return false; //string match went wrong
//all the matched characters have been consumed
//continue
break;

case '[': if(!osc_matchCharList(p, plen, s, slen)) return false; //char list match went wrong
//all the matched characters have been consumed
//continue
break;

case '}':
case ']':
return false; //invalid, must have been consumed by the subroutines

case '/': //need to be first, which we already checked, this means we are in a new section, OR this is the // case
{
if(*plen == plen_)
{
// "//" case
ctx |= 1; //mark deep match

//skip all sections until nothing left or first match found

//store return conditions
const char* pp = *p+1; //pass '/'
int pplen = (*plen)-1;
const char* ss = *s;
int sslen = *slen;

if(pplen<=0) return false; //if no more pattern follows the second '/', this is an error.. a pattern ending in "....//" is illegal
if(sslen<=0) return false; //empty name is error

while(1)
{
//entering recursive star mode, saving previous conditions
if(osc_matchPattern0(&pp, &pplen, &ss, &sslen, ctx)) //may encounter yet another "//" (needt to use the Raw osc_matchPattern0 here, because the name already lacks the leading '/'
{
//restore normal mode with new conditions
*p = pp; *plen = pplen;
*s = ss; *slen = sslen;

ctx &= ~1; //kill mark of deep match
return true; //no break here, the recursed function will have resumed the matching itself
}
else
{
//match went wrong, try match deeper in the name sections by skipping one
while(sslen>0) { if(*ss == '/') { break; } --sslen; ++ss; }
if(sslen<=0) return false; //no name left but pattern goes on

--sslen; ++ss; //pass the found section begin '/'

//found another name section
//retry
}
}

break;
}
else
{
//end of a pattern section reached, which marks the start of a new one
//recurse
//return osc_matchPatternImpl(p, plen, s, slen, ctx);

//consume and compare, to prevent recursion, technically, it is a
//fallthrough
}
}
/* no break */
default: //match a single s character, if available
{
if(*slen<=0) return false;
if(**p != **s) return false; //TEST
--*plen; ++*p; --*slen; ++*s; //consume
//continue
}
break;
} //switch

} //while pattern left to check

//no more pattern left to check

if(*slen>0) return false; //name left over unchecked

return true;
}

/**
* main osc matching function
* matching an osc pattern to an osc name or path, both including leading '/'.
* several sections of /.../.../... are allowed in both.
*
* it is recommended to call this function via @osc_matchPattern
*
* if match is successful, both the matched pattern (including possible [...], {...}, '*', "//" etc) and the matched name portion will be 'consumed', meaning that
* the pointers will have advanced, the length information will be adjusted (decreased by that much too)
* otherwise, the pointers will remain at the last matched position.
*
* @param p pointer to char pointer for the pattern to match, keeps advancing while matching (starting with '/')
* @param plen pointer to the pattern length, keeps decrementing while matching (including leading '/')
* @param s pointer to char pointer for the name to match, keeps advancing while matching (starting with '/')
* @param slen pointer to the name length, keeps decrementing while matching (including leading '/')
*
* @return true if pattern matches the adress, false otherwise
*/
static bool osc_matchPatternImpl(const char** const p, int* const plen, const char** const s, int* const slen, unsigned int ctx)
{
//make sure we start with '/' on both sides
if(*plen<=0) return false;
if(*slen<=0) return false;
if(**p != '/') return false;
if(**p != **s) return false; //TEST
--*plen; ++*p; --*slen; ++*s; //consume
//continue

return osc_matchPattern0(p, plen, s, slen, ctx);
}

/**
* main osc matching function
* matching an osc pattern to an osc name or path, both including leading '/'.
* several sections of /.../.../... are allowed in both.
*
* @param p pointer to char pointer for the pattern to match, keeps advancing while matching (starting with '/')
* @param plen pointer to the pattern length, keeps decrementing while matching (including leading '/')
* @param s pointer to char pointer for the name to match, keeps advancing while matching (starting with '/')
* @param slen pointer to the name length, keeps decrementing while matching (including leading '/')
*
* @return true if pattern matches the adress, false otherwise
*/
bool osc_matchPattern(const char *pPatt, int pPattLen, const char *pName, int pNameLen)
{
unsigned int ctx = 0; //for future use
return osc_matchPatternImpl(&pPatt, &pPattLen, &pName, &pNameLen, ctx);
}

//// call in / glue code for tinyosc

#include <string.h>
#include "tinyosc_match.h"

bool tosc_matchAddress(tosc_message *o, const char* pcName) {
const char* pcPatt = tosc_getAddress(o);
int pLen = (int)strlen(pcPatt);
int nLen = (int)strlen(pcName);

// printf("OSC Matching> p: %s (%d) with %s (%d)\n", pcPatt, pLen, pcName, nLen);
return osc_matchPattern(pcPatt, pLen, pcName, nLen);
}
16 changes: 16 additions & 0 deletions tinyosc_match.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* tinyosc_match.h
*
* Created on: May 17, 2022
* Author: master
*/

#ifndef MAIN_TINYOSC_MATCH_H_
#define MAIN_TINYOSC_MATCH_H_

#include <stdbool.h>
#include "tinyosc.h"

bool tosc_matchAddress(tosc_message *o, const char* pcName);

#endif /* MAIN_TINYOSC_MATCH_H_ */