This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 19803 - "Fingerprint" is unclear
Summary: "Fingerprint" is unclear
Status: CLOSED FIXED
Alias: None
Product: AudioWG
Classification: Unclassified
Component: MIDI API (show other bugs)
Version: unspecified
Hardware: All All
: P2 normal
Target Milestone: TBD
Assignee: Chris Wilson
QA Contact: public-audio
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2012-10-31 20:39 UTC by Chris Wilson
Modified: 2013-01-02 11:35 UTC (History)
2 users (show)

See Also:


Attachments

Description Chris Wilson 2012-10-31 20:39:04 UTC
The "should" description of fingerprint makes it unreliable, and it is not clear how it might fail.
Comment 1 Jussi Kalliokoski 2012-11-01 10:04:45 UTC
(In reply to comment #0)
> The "should" description of fingerprint makes it unreliable, and it is not
> clear how it might fail.

Yeah, at least AFAICT there's no way of actually guaranteeing that the reliability of the fingerprint. IIRC on Windows XP you don't even have access to the names of USB MIDI devices. And even when you do, it's really hard to determine that if the user has 2 pieces of the same MIDI device, which one is plugged. Hence it's not a hard requirement that the fingerprint stays the same for that certain port, because it might not be possible to achieve, but it's still better than nothing and very useful for example for DAW software, because it can be made to work most of the time.

Maybe we should add a note to the spec describing the rationale here?
Comment 2 Chris Wilson 2012-11-01 17:42:41 UTC
I think most DAW software just uses the index offset and the device name to guess; that's all you get in Windows.  This is why I'm suggesting maybe we should simply remove fingerprint.
Comment 3 Jussi Kalliokoski 2012-12-05 10:13:12 UTC
(In reply to comment #2)
> I think most DAW software just uses the index offset and the device name to
> guess; that's all you get in Windows.  This is why I'm suggesting maybe we
> should simply remove fingerprint.

That's incorrect. Through the Windows' MIDI APIs, sure, that's all you get, but you can get more information about the device via other APIs. I don't think we should make our design choices based on what the Windows MIDI APIs (ie. the lowest common denominator) provide us, especially since there are ways to get around these limitations, they're just not very convenient.  I don't want our API to be as annoying to use as the Windows MIDI APIs.

A native application designer can go and get this information about the devices themselves to improve the user experience, a web application designer can't. I think it's imperative that we provide features like this from the v1 to provide the developers with the tools to design the best user experience possible.
Comment 4 Chris Wilson 2012-12-05 18:36:40 UTC
TODO: Detail how implementations should "fake" uniqueness when it's not available, and how developers should react to failures.
Comment 5 Chris Wilson 2012-12-05 20:34:12 UTC
Resolved: keeping fingerprint, better describing matching.  https://dvcs.w3.org/hg/audio/rev/f572fe073d58
Comment 6 Jussi Kalliokoski 2012-12-07 20:19:46 UTC
(In reply to comment #5)
> Resolved: keeping fingerprint, better describing matching. 
> https://dvcs.w3.org/hg/audio/rev/f572fe073d58

The current wording doesn't quite communicate what I was proposing at the teleconf:

If the system detects (for example) two devices attached to the user's computer with the exact same pool of data used to generate the fingerprint, the second one's fingerprint generation would inject information (of the previous attempted fingerprint not being unique) to theentropy pool, and a third device would do the same, and so on, thus maintaining association unless the order of the devices changes.

This, I think, is the best guess you can get at uniquely identifying a device, and the user agent doesn't need to maintain or store any information about devices the user has had. A possible implementation in JavaScript that would catch the gist of that:

var devices = new Map()

function createFingerprintForDevice(device) {
  var fingerprint
  var poolExtra = ''

  do {
    fingerprint = createUUID(device.entropyPool + poolExtra)
    poolExtra += '#existed'
  } while (devices.has(fingerprint))

  devices.set(fingerprint, device)

  return fingerprint
}

Not sure how to word this in the spec. Would it be worth dictating this, regardless of how good the implementation is at producing unique fingerprints?
Comment 7 Chris Wilson 2012-12-07 21:10:24 UTC
(In reply to comment #6)
> Not sure how to word this in the spec. Would it be worth dictating this,
> regardless of how good the implementation is at producing unique
> fingerprints?

Hmm.  Well, the problem is that you can't come up with an ideal identifier, right?  If I record in the fingerprint "this is MOTU MIDI Express input port #1 of 16", and then when I go to recreate based on that fingerprint there are only 8 ports with that name/manufacturer/version, I don't know if #1 of 8 is the right port or not (I don't know if they disconnected the first interface/8 ports or the second 8).  I couldn't think of an algorithm good enough to dictate; the best I could do would be "look for a name match, and match "#n of m ports with this name" - if the number of ports with this name ("m") has changed, just fail.  That would take care of "unplugging my AKAI USB keyboard controller doesn't make my Launchpad assignments stop working," but the above case (I unplug one of my identically-named interfaces) would fail.  What do you think of that?  Your code example is missing too many function implementations for me to completely confirm, but I think that's essentially what it's doing?
Comment 8 Jussi Kalliokoski 2012-12-07 22:52:59 UTC
(In reply to comment #7)
> Your code example is missing too many
> function implementations for me to completely confirm, but I think that's
> essentially what it's doing?

Well, the function definitions don't really matter, it could be:

var createUUID = (str) => str

Port.prototype = {
  get entropyPool () {
    return [
      this.name,
      this.manufacturer,
      this.version,
      this.type
    ].join(String.fromCharCode(0))
  }
}

Although that's not a very appealing unique identifier. The important part is that createUUID is deterministic. And oops, I meant 'port', not 'device in the the original example.

Anyway, that's not what I meant. In cases where the user has changed the port ordering and the ports can't be uniquely identified, things will go wrong, there's nothing that can be done to prevent this. But the algorithm I just typed gives correct behavior in for example this case:

Ports A and B have the same entropy pool. Port B gets a fingerprint that has something added to its entropy pool to indicate that it's natural fingerprint was taken. The application uses both A and B. The user disconnects port A, and its fingerprint gets freed, while port B still has the same fingerprint that was associated to it for the session. The user reconnects port A and here, magic happens, the algorithm tries if the natural fingerprint for the port is available, and it is because port B has a modified entropy pool, so the application can associate port A back to its rightful task, even though the port ordering has possibly been flipped. Of course, when the user restarts the application, the ports will be flipped but there's really nothing we can do about that. Sound good? I don't think it's possible to get a better fallback mechanism than this.
Comment 9 Jussi Kalliokoski 2012-12-07 23:05:03 UTC
Actually, I just made an assumption that I'm not sure is valid with all platform MIDI APIs: if you unplug device at index 0 while the application is running, your reference to a device that was in index 1 still works. I can't test this right now, I only have 1 MIDI device home at the moment, does anyone know better?
Comment 10 Chris Wilson 2012-12-07 23:07:10 UTC
(In reply to comment #8)
> Ports A and B have the same entropy pool. Port B gets a fingerprint that has
> something added to its entropy pool to indicate that it's natural
> fingerprint was taken. The application uses both A and B. The user
> disconnects port A, and its fingerprint gets freed, while port B still has
> the same fingerprint that was associated to it for the session. The user
> reconnects port A and here, magic happens, the algorithm tries if the
> natural fingerprint for the port is available, and it is because port B has
> a modified entropy pool, so the application can associate port A back to its
> rightful task, even though the port ordering has possibly been flipped. 

I don't see how you're going to maintain that port B - in the enumeration of MIDI ports coming form the underlying hardware, not in the fingerprint stored by the app in local storage - has a modified entropy pool.  Every time you enumerate ports, you're going to start from zero with the fingerprint map - so if A was removed, B will be the "natural fingerprint available" port.  Yes?

I think we can suggest matching based on the available info - port name, manufacturer, version, even index of same - but if one of those ports is missing (aka, to use your example, "there are not the same number of ports with that natural fingerprint"), then all bets are off, and the match should likely fail.  At the very least, that lets the DAW grab the exception and implement their own hook.  I think that's captured in the current text, but not as "MUST".

In short: I think we can say "deal with the case when the port name/etc. are unique" - we can even say "deal with the case when the port name/etc. are not unique, but the number of ports that match that doesn't change"; but if the number of ports that match that "natural UUID" is different, it's going to have to fail.
Comment 11 Chris Wilson 2012-12-07 23:13:39 UTC
(In reply to comment #9)
> Actually, I just made an assumption that I'm not sure is valid with all
> platform MIDI APIs: if you unplug device at index 0 while the application is
> running, your reference to a device that was in index 1 still works. I can't
> test this right now, I only have 1 MIDI device home at the moment, does
> anyone know better?

I know in Windows MIDI, once you've grabbed a reference to the device (via MidiInOpen/MidiInGetID/etc), you have an HMIDIIN or HMIDIOUT handle, which doesn't change (should continue to work) - but of course, if you enumerated the devices in a list via midiInGetNumDevs(), called midiInGetDevCaps() to get the name/etc. BUT NOT A HMIDIIN, THEN disconnected, the list indices are going to be off.

I think it's similar in CoreMIDI - it all depends when you call MIDIGetDevice().
Comment 12 Jussi Kalliokoski 2012-12-07 23:42:48 UTC
(In reply to comment #10)
> I don't see how you're going to maintain that port B - in the enumeration of
> MIDI ports coming form the underlying hardware, not in the fingerprint
> stored by the app in local storage - has a modified entropy pool.  Every
> time you enumerate ports, you're going to start from zero with the
> fingerprint map - so if A was removed, B will be the "natural fingerprint
> available" port.  Yes?

I need to actually try this to be sure, but for example the Windows MIDI API has the notion of device ID, which is actually a pointer to a HMIDIIN instance, and it seems that even if the port's index has changed, the ID stays the same, so you can actually see that port at index something has a pointer that's already associated to a fingerprint. But I need to figure out if this is true, and if something like this can be done on (at least) all desktop platforms. It would be silly if you can't detect whether two midi port instances are actually pointing to the same port.
Comment 13 Jussi Kalliokoski 2012-12-07 23:44:47 UTC
(In reply to comment #12)
> (In reply to comment #10)
> > I don't see how you're going to maintain that port B - in the enumeration of
> > MIDI ports coming form the underlying hardware, not in the fingerprint
> > stored by the app in local storage - has a modified entropy pool.  Every
> > time you enumerate ports, you're going to start from zero with the
> > fingerprint map - so if A was removed, B will be the "natural fingerprint
> > available" port.  Yes?
> 
> I need to actually try this to be sure, but for example the Windows MIDI API
> has the notion of device ID, which is actually a pointer to a HMIDIIN
> instance, and it seems that even if the port's index has changed, the ID
> stays the same, so you can actually see that port at index something has a
> pointer that's already associated to a fingerprint. But I need to figure out
> if this is true, and if something like this can be done on (at least) all
> desktop platforms. It would be silly if you can't detect whether two midi
> port instances are actually pointing to the same port.

(In reply to comment #11)
> (In reply to comment #9)
> > Actually, I just made an assumption that I'm not sure is valid with all
> > platform MIDI APIs: if you unplug device at index 0 while the application is
> > running, your reference to a device that was in index 1 still works. I can't
> > test this right now, I only have 1 MIDI device home at the moment, does
> > anyone know better?
> 
> I know in Windows MIDI, once you've grabbed a reference to the device (via
> MidiInOpen/MidiInGetID/etc), you have an HMIDIIN or HMIDIOUT handle, which
> doesn't change (should continue to work) - but of course, if you enumerated
> the devices in a list via midiInGetNumDevs(), called midiInGetDevCaps() to
> get the name/etc. BUT NOT A HMIDIIN, THEN disconnected, the list indices are
> going to be off.
> 
> I think it's similar in CoreMIDI - it all depends when you call
> MIDIGetDevice().

Yeah, I just tried with a virtual device and it works like this on Linux/ALSA as well.
Comment 14 Chris Wilson 2012-12-08 00:46:58 UTC
(In reply to comment #12)
> I need to actually try this to be sure, but for example the Windows MIDI API
> has the notion of device ID, which is actually a pointer to a HMIDIIN
> instance, and it seems that even if the port's index has changed, the ID
> stays the same, so you can actually see that port at index something has a
> pointer that's already associated to a fingerprint. But I need to figure out
> if this is true, and if something like this can be done on (at least) all
> desktop platforms. 

Hmm, yes.  Would have to see what happens with device IDs when doing the unplugging dance.  I don't have a Windows instance handy or I'd test.


> It would be silly if you can't detect whether two midi
> port instances are actually pointing to the same port.

Well, we don't currently have a way to do that; I don't think we should be saying that we'll return the same MIDIInput *instance*, for example, or people are going to step on their own onmessage handlers.  Or we need to explicitly say that.  Actually, we probably should explicitly say that either way.  Does that make sense?

If we do make MIDIPorts have singular instances, I should point out that we would NOT be able to make the equality work across Worker boundaries, if we take that on.  Not sure it really matters.
Comment 15 Jussi Kalliokoski 2012-12-09 11:01:33 UTC
(In reply to comment #14)
> Hmm, yes.  Would have to see what happens with device IDs when doing the
> unplugging dance.  I don't have a Windows instance handy or I'd test.

Ahh, I just tried it, and of course, I'd forgotten about this! On Windows, you can only have one handle per port, trying to get another will give you an error, so once you do a midiInOpen(), your HMIDIIN handle becomes an exclusive access to that device. What a PITA, but at least it makes my algorithm work on Windows.

> Well, we don't currently have a way to do that; I don't think we should be
> saying that we'll return the same MIDIInput *instance*, for example, or
> people are going to step on their own onmessage handlers.  Or we need to
> explicitly say that.  Actually, we probably should explicitly say that
> either way.  Does that make sense?

Oh nono, that's exactly what the fingerprint does, it allows you to see if two port instances are referring to the same port. But yes, equality between two instances of the port might have ugly consequences, and is not a good idea.

> If we do make MIDIPorts have singular instances, I should point out that we
> would NOT be able to make the equality work across Worker boundaries, if we
> take that on.  Not sure it really matters.

I think it's best that equality is measured by comparing the fingerprints rather than instances in this case.
Comment 16 Chris Wilson 2012-12-10 17:54:37 UTC
(In reply to comment #15)

Tweaked the wording to try to make these goals more clear.  https://dvcs.w3.org/hg/audio/rev/d61cc0adee7e.
Comment 17 Olivier Thereaux 2013-01-02 11:35:06 UTC
(In reply to comment #16)
> (In reply to comment #15)
> 
> Tweaked the wording to try to make these goals more clear. 
> https://dvcs.w3.org/hg/audio/rev/d61cc0adee7e.

hearing no objection to the changesets mentioned above, closing.