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

Support AND based scan filtering #586

Closed
twyatt opened this issue Oct 11, 2023 · 10 comments · Fixed by #695
Closed

Support AND based scan filtering #586

twyatt opened this issue Oct 11, 2023 · 10 comments · Fixed by #695

Comments

@twyatt
Copy link
Member

twyatt commented Oct 11, 2023

Currently, filters are specified in a list and scan results are filtered by if they match any of the specified filters (OR based), for example:

Scanner {
    filters = listOf(
        Filter.Name("Example"),
        Filter.Service(uuidFrom("96f630b5-7ee4-4e47-a550-ec0d0da5c487")),
    }
}

Will match any advertisements with the name "Example" OR service UUID "96f630b5-7ee4-4e47-a550-ec0d0da5c487".

Kable should support AND based filtering, for example:

Scanner {
    filters = listOf(
        Filter.Name("Example") and Filter.Service(uuidFrom("2001b5ee-9bf6-48da-a6e8-2901865c0435")),
    }
}

Will only match advertisements that have both the name "Example" AND the service UUID "2001b5ee-9bf6-48da-a6e8-2901865c0435".

The Web Bluetooth spec provides some nice examples of AND vs. OR matching.

@Entreco
Copy link

Entreco commented Dec 15, 2023

Maybe we can create a Filter.All type, and if that is present, we use AND?

So

// Matches on ALL filters
filters = listOf(
        Filter.All,
        Filter.Name("Example"),
        Filter.Service(uuidFrom("2001b5ee-9bf6-48da-a6e8-2901865c0435")),
    }
    
// Matches with current default OR style
filters = listOf(
        Filter.Name("Example"),
        Filter.Service(uuidFrom("2001b5ee-9bf6-48da-a6e8-2901865c0435")),
    }

This solution should be backwards compatible. Users that want to use the AND based filters, only need to add Filter.All and should be good to go.

Let me know if this could work for you, and the exact name you would prefer? Filter.All, Filter.MatchAll, Filter.AndBased (naming is the most difficult thing in Software you know :) ) I'm happy to create a PR for you, if you agree to this approach.

@twyatt
Copy link
Member Author

twyatt commented Dec 15, 2023

I'm not sure if it is feasible, but I was thinking of using or and and syntax, for example:

filters = Filter.Name("Example") and Filter.Service(uuidFrom(..))

They could even be combined, for example:

filters = (Filter.Name("Example") or Filter.Name("Test")) and Filter.Service(uuidFrom(..)

...this unfortunately would not be backwards compatible, but could have a deprecation cycle where the old behavior is moved to another property as the and + or feature is introduced. 🤷

Maybe we could introduce the new syntax as matches property. 🤔

@Entreco
Copy link

Entreco commented Dec 19, 2023

Yeah, I see what you mean. The native filtering options, make my suggestion a bit clumsy to implement.
Let me see if I can start to work on this using your suggested approach

@twyatt
Copy link
Member Author

twyatt commented Feb 21, 2024

@Entreco this has been slated (internally) for our next sprint.
Work should begin in the coming weeks.

@davidtaylor-juul
Copy link
Contributor

What would it mean to specify a filter like this?

filters = Filter.Name("Example") and Filter.Name("Other")

We would probably have to reject this with an exception at runtime?

I am wondering if it would be simpler and cleaner to provide a product type that expresses the range of possible filters instead of attempting to write combinators to make up for the lack of expression with the existing sum type.

@twyatt
Copy link
Member Author

twyatt commented Apr 3, 2024

We would probably have to reject this with an exception at runtime?

Oof, definitely not ideal.

I am wondering if it would be simpler and cleaner to provide a product type that expresses the range of possible filters instead of attempting to write combinators to make up for the lack of expression with the existing sum type.

Interesting, can you give an example of what you think that might look like?

@davidtaylor-juul
Copy link
Contributor

I was thinking about this type (simplified for brevity):

sealed class Filter {
    class Service(uuid: Uuid) : Filter()
    class Name(name: String) : Filter()
}

when considered in isolation implies ONE OF a Service, Name etc. (i.e. a sum-type)

If we have some collection of these, we need to figure out a way to define whether to "OR" or "AND" that collection together, because the underlying type provides no guidance for how to combine them.

Instead, if we change the shape of the type to something like this:

class Filter(
    val services: List<Uuid>,
    val name: String?,
)

when considered in isolation implies Service(s) AND (optionally) Name etc. (i.e. a product-type)

This type is providing an inherent "AND". (Aside: I suppose that we are, in fact, deliberately choosing to "AND" the properties together, but it feels like the expected behavior.)

If we then have a collection of these, it necessarily implies that they be "OR"ed together, because an "AND" operation would be non-sensical.

I haven't thought through all the options, but it seems like a type of this shape provides an API that more naturally models the problem space. And of course I didn't conclude this in isolation, but came to realize it after messing around with the W3C API!

I do like the flexibility of the proposed and/or operators on the sealed class, and I like that it would be a pretty cool API to have. On the other hand it smells a bit over-engineered, and requires those ugly runtime heuristics.

It would be a fun exercise to implement it anyway, just to see how it shakes out.

For the API consumer, the difference would be:

listOf(Filter(services = listOf(uuid1)), Filter(services = listOf(uuid2), name = "bob"))

vs

Service(uuid1) or (Service(uuid2) and Name("bob"))

The latter is much prettier, but then also allows for "incorrect" expressions. I'm leaning towards ugly-but-functional-and-less-work myself.

@davidtaylor-juul
Copy link
Contributor

After typing out the above I woke up in the middle of the night with the realization that I had accidentally worked out a solution, which is to combine both types. Will throw up a draft PR today of the concept.

@Entreco
Copy link

Entreco commented Apr 5, 2024

My personal need for this feature has vanished, but i'm happy to review/test/brainstorm any drafts.

I'll keep an eye out for updates

@davidtaylor-juul
Copy link
Contributor

Will throw up a draft PR today of the concept.

Welp, that didn't pan out and here we are some six weeks later 😬 Good news is we found a practical resolution to the issues: #695

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants