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

FileTransfer #8

Open
5 tasks
icedream opened this issue Jul 14, 2021 · 96 comments
Open
5 tasks

FileTransfer #8

icedream opened this issue Jul 14, 2021 · 96 comments

Comments

@icedream
Copy link
Owner

  • Reveng'ing the protocol
  • PoC for protocol implementation
  • Drafting API
  • Restructuring PoC to conform with drafted API
  • Testing
@icedream
Copy link
Owner Author

Indented is stream from client, non-indented is stream from Denon Prime 4

00000000  00 00 00 00 03 95 4e 03  d3 fa 4e 0b 82 f2 fa 15   ......N. ..N.....
00000010  31 7b 03 01 00 00 00 18  00 46 00 69 00 6c 00 65   1{...... .F.i.l.e
00000020  00 54 00 72 00 61 00 6e  00 73 00 66 00 65 00 72   .T.r.a.n .s.f.e.r
00000030  a9 2f                                              ./
00000032  00 00 00 12 66 6c 74 78  00 00 00 00 00 00 00 08   ....fltx ........
00000042  00 00 00 02 00 31                                  .....1
00000048  00 00 00 10 66 6c 74 78  00 00 00 02 00 00 07 d2   ....fltx ........
00000058  00 00 00 00                                        ....
    00000000  00 00 00 13 66 6c 74 78  00 00 00 02 00 00 00 03   ....fltx ........
    00000010  00 00 00 00 01 01 00                               .......
    00000017  00 00 00 20 66 6c 74 78  00 00 00 64 00 00 00 08   ... fltx ...d....
    00000027  00 00 00 10 00 54 00 69  00 6d 00 65 00 43 00 6f   .....T.i .m.e.C.o
    00000037  00 64 00 65                                        .d.e
    0000003B  00 00 00 10 66 6c 74 78  00 00 00 65 00 00 07 d2   ....fltx ...e....
    0000004B  00 00 00 00                                        ....
0000005C  00 00 00 65 66 6c 74 78  00 00 00 65 00 00 00 03   ...efltx ...e....
0000006C  00 00 00 02 00 00 00 26  00 49 00 63 00 65 00 64   .......& .I.c.e.d
0000007C  00 72 00 65 00 61 00 6d  00 53 00 53 00 44 00 20   .r.e.a.m .S.S.D. 
0000008C  00 28 00 55 00 53 00 42  00 20 00 31 00 29 00 00   .(.U.S.B . .1.)..
0000009C  00 24 00 41 00 4e 00 41  00 52 00 43 00 48 00 59   .$.A.N.A .R.C.H.Y
000000AC  00 55 00 53 00 42 00 20  00 28 00 55 00 53 00 42   .U.S.B.  .(.U.S.B
000000BC  00 20 00 31 00 29 01 01  01 00 00 00 19 66 6c 74   . .1.).. .....flt
000000CC  78 00 00 00 65 00 00 00  02 01 00 00 00 00 7f ff   x...e... ........
000000DC  ff ff ff ff ff ff                                  ......
    0000004F  00 00 00 60 66 6c 74 78  00 00 00 66 00 00 07 d1   ...`fltx ...f....
    0000005F  00 00 00 50 00 2f 00 49  00 63 00 65 00 64 00 72   ...P./.I .c.e.d.r
    0000006F  00 65 00 61 00 6d 00 53  00 53 00 44 00 20 00 28   .e.a.m.S .S.D. .(
    0000007F  00 55 00 53 00 42 00 20  00 31 00 29 00 2f 00 45   .U.S.B.  .1.)./.E
    0000008F  00 6e 00 67 00 69 00 6e  00 65 00 20 00 4c 00 69   .n.g.i.n .e. .L.i
    0000009F  00 62 00 72 00 61 00 72  00 79 00 2f 00 6d 00 2e   .b.r.a.r .y./.m..
    000000AF  00 64 00 62                                        .d.b
000000E2  00 00 00 41 66 6c 74 78  00 00 00 66 00 00 00 01   ...Afltx ...f....
000000F2  01 00 00 00 77 55 00 00  00 00 00 25 87 12 00 c4   ....wU.. ...%....
00000102  46 18 00 00 00 00 00 00  25 87 12 00 c4 46 18 00   F....... %....F..
00000112  00 00 00 00 00 25 4b cf  00 36 ee 80 00 00 00 00   .....%K. .6......
00000122  00 00 f8 40 00                                     ...@.
    000000B3  00 00 00 64 66 6c 74 78  00 00 00 66 00 00 07 d4   ...dfltx ...f....
    000000C3  00 00 00 50 00 2f 00 49  00 63 00 65 00 64 00 72   ...P./.I .c.e.d.r
    000000D3  00 65 00 61 00 6d 00 53  00 53 00 44 00 20 00 28   .e.a.m.S .S.D. .(
    000000E3  00 55 00 53 00 42 00 20  00 31 00 29 00 2f 00 45   .U.S.B.  .1.)./.E
    000000F3  00 6e 00 67 00 69 00 6e  00 65 00 20 00 4c 00 69   .n.g.i.n .e. .L.i
    00000103  00 62 00 72 00 61 00 72  00 79 00 2f 00 6d 00 2e   .b.r.a.r .y./.m..
    00000113  00 64 00 62 00 00 00 00                            .d.b.... 
00000127  00 00 00 18 66 6c 74 78  00 00 00 66 00 00 00 04   ....fltx ...f....
00000137  00 00 00 00 00 f8 40 00  00 00 00 01               ......@. ....
    0000011B  00 00 00 24 66 6c 74 78  00 00 00 66 00 00 07 d5   ...$fltx ...f....
    0000012B  00 00 00 00 00 00 00 01  00 00 00 00 00 00 00 00   ........ ........
    0000013B  00 00 00 00 00 00 00 00                            ........ 
00000143  00 00 10 18 66 6c 74 78  00 00 00 66 00 00 00 05   ....fltx ...f....
00000153  00 00 00 00 00 00 00 00  00 00 10 00 53 51 4c 69   ........ ....SQLi
00000163  74 65 20 66 6f 72 6d 61  74 20 33 00 10 00 01 01   te forma t 3.....
[...]
00001113  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00001123  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00001133  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00001143  00 00 00 00 00 00 30 4f  00 00 00 2f 49 00 00 00   ......0O .../I...
00001153  2e 40 00 00 00 19 32 00  00 00 18 11               [email protected]. ....
    00000143  00 00 00 24 66 6c 74 78  00 00 00 66 00 00 07 d5   ...$fltx ...f....
    00000153  00 00 00 00 00 00 00 01  00 00 00 00 00 00 00 17   ........ ........
    00000163  00 00 00 00 00 00 00 17                            ........ 
0000115F  00 00 10 18 66 6c 74 78  00 00 00 66 00 00 00 05   ....fltx ...f....
0000116F  00 00 00 00 00 01 70 00  00 00 10 00 0d 00 00 00   ......p. ........
0000117F  11 01 f9 00 01 f9 0f d5  0f 83 0e 60 0d 6b 0e 2f   ........ ...`.k./
0000118F  0c 4f 0d 2c 0b d3 0a da  09 c6 0a b1 08 2e 06 d4   .O.,.... ........
0000119F  05 5b 04 c4 04 51 00 00  00 00 00 00 00 00 00 00   .[...Q.. ........
000011AF  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
[...]
000020FF  03 06 17 2b 2b 01 59 74  61 62 6c 65 73 71 6c 69   ...++.Yt ablesqli
0000210F  74 65 5f 73 65 71 75 65  6e 63 65 73 71 6c 69 74   te_seque ncesqlit
0000211F  65 5f 73 65 71 75 65 6e  63 65 04 43 52 45 41 54   e_sequen ce.CREAT
0000212F  45 20 54 41 42 4c 45 20  73 71 6c 69 74 65 5f 73   E TABLE  sqlite_s
0000213F  65 71 75 65 6e 63 65 28  6e 61 6d 65 2c 73 65 71   equence( name,seq
0000214F  29 29 02 06 17 3d 17 01  00 69 6e 64 65 78 73 71   ))...=.. .indexsq
0000215F  6c 69 74 65 5f 61 75 74  6f 69 6e 64 65 78 5f 54   lite_aut oindex_T
0000216F  72 61 63 6b 5f 31 54 72  61 63 6b 03               rack_1Tr ack.
    0000016B  00 00 00 24 66 6c 74 78  00 00 00 66 00 00 07 d5   ...$fltx ...f....
    0000017B  00 00 00 00 00 00 00 01  00 00 00 00 00 00 00 18   ........ ........
    0000018B  00 00 00 00 00 00 00 18                            ........ 
0000217B  00 00 10 18 66 6c 74 78  00 00 00 66 00 00 00 05   ....fltx ...f....
0000218B  00 00 00 00 00 01 80 00  00 00 10 00 0d 00 00 00   ........ ........
0000219B  21 00 73 00 03 b0 03 5e  03 00 02 8c 0f 76 0e e3   !.s....^ .....v..
[...]
0000316B  44 61 74 61 62 61 73 65  20 4f 4e 20 54 72 61 63   Database  ON Trac
0000317B  6b 20 28 20 75 75 69 64  4f 66 45 78 74 65 72 6e   k ( uuid OfExtern
0000318B  61 6c 44 61 74 61 62 61  73 65 20 29               alDataba se )
    00000193  00 00 00 24 66 6c 74 78  00 00 00 66 00 00 07 d5   ...$fltx ...f....
    000001A3  00 00 00 00 00 00 00 01  00 00 00 00 00 00 00 2d   ........ .......-
    000001B3  00 00 00 00 00 00 00 2d                            .......- 
00003197  00 00 10 18 66 6c 74 78  00 00 00 66 00 00 00 05   ....fltx ...f....
000031A7  00 00 00 00 00 02 d0 00  00 00 10 00 0d 00 00 00   ........ ........
000031B7  0e 00 6a 00 0e d8 0d b1  0c c8 0b f4 0b 08 0a 28   ..j..... .......(
[...]
00004187  6c 2e 74 79 70 65 20 3d  20 6c 74 6c 2e 6c 69 73   l.type =  ltl.lis
00004197  74 54 79 70 65 20 57 48  45 52 45 20 6c 74 6c 2e   tType WH ERE ltl.
000041A7  6c 69 73 74 54 79 70 65  20 3d 20 32               listType  = 2
    000001BB  00 00 00 24 66 6c 74 78  00 00 00 66 00 00 07 d5   ...$fltx ...f....
    000001CB  00 00 00 00 00 00 00 01  00 00 00 00 00 00 00 2e   ........ ........
    000001DB  00 00 00 00 00 00 00 2e                            ........ 
000041B3  00 00 10 18 66 6c 74 78  00 00 00 66 00 00 00 05   ....fltx ...f....
000041C3  00 00 00 00 00 02 e0 00  00 00 10 00 0d 09 ee 00   ........ ........
000041D3  09 00 f1 00 08 e5 07 c1  06 d9 05 d4 04 c7 03 32   ........ .......2
[... ... ...]
000102B7  ba 7f 04 02 01 0f 00 f3  10 31 08 8d ba 7e 04 02   ........ .1...~..
000102C7  01 0f 00 f3 0f 31 0b 8d  ba 7d 04 02 01 15 00 f3   .....1.. .}......
000102D7  0d 66 6c 61 63 08 8d ba  7c 04 02 01 0f 00 f3 0c   .flac... |.......
000102E7  30 0c 8d ba 7b 04 02 01  17 00 f3 0a 30 33 3a 34   0...{... ....03:4
000102F7  30 07 8d ba 7a 04 02 01  00 00 f3 09               0...z... ....
    0000039B  00 00 00 0c 66 6c 74 78  00 00 00 66 00 00 07 d6   ....fltx ...f....

@MarByteBeep
Copy link

MarByteBeep commented Aug 2, 2021

Some things I observed:

  • When also subscribing to the FileTransfer service, after 5 seconds the StateMap service suddenly outputs all states to be 0xFFFF FFFF (== -1?), instead of a JSON string. This doesn't happen when only subscribing to the StateMap service. I'll account for that in my StateMap handler, which until now always assumed the subsequent read to be a JSON, not 0xFFFF FFFF.

  • When subscribing to FileTransfer and not sending anything to it, I always get a fltx magic marker first + the following response:

[17:07:15] [LOG] <Buffer 00 00 00 00 00 00 00 08 00 00 00 02 00 31>
[17:07:15] [LOG] <Buffer 00 00 00 4a 00 00 07 d2 00 00 00 00>
[17:07:17] [LOG] <Buffer 00 00 00 4a 00 00 07 d2 00 00 00 00>
...

and that last pattern repeats indefinitely every 2 or so seconds. Strangely I've also observed the following repeating pattern:

[18:00:31] [LOG] <Buffer 00 00 00 01 00 00 07 d2 00 00 00 00>

so it has 0x01 as opposed to 0x4a. No idea why :)

  • When the StateMap services received an update, it starts with a smaa magic marker. When this marker is followed by 0x0000 0000, I expect a name + JSON. But when it is followed by 0x0000 07d2, I get a name + uint32. This happened in my first example. In your code you named this uint32 interval. Did you see any other values other than 0x0 and 0xFFFF FFFF?

  • Coincidentally that 0x0000 07d2 pattern can also be observed in those FileTransfer responses...

@MarByteBeep
Copy link

Full output of the above example when I also subscribe to FileTransfer in my code:

[17:43:12] [INFO] Found 'primego' Controller at '192.168.178.34:32913' with following software: { name: 'JP11', version: '1.6.2' }
[17:43:12] [LOG] TCP connection to 'primego:32913' local port: 10768
[17:43:12] [INFO] Discovered the following services:
[17:43:12] [INFO] 	port: 41839 => StateMap
[17:43:12] [INFO] 	port: 42039 => Broadcast
[17:43:12] [INFO] 	port: 40077 => TimeSynchronization
[17:43:12] [INFO] 	port: 41909 => BeatInfo
[17:43:12] [INFO] 	port: 44819 => FileTransfer
[17:43:13] [LOG] TCP connection to 'primego:41839' local port: 10769
[17:43:13] [INFO] Connected to service 'StateMap' at port 41839
[17:43:13] [LOG] /Mixer/CH1faderPosition => {"type":0,"value":0}
[17:43:13] [LOG] /Mixer/CH2faderPosition => {"type":0,"value":0.5699999928474426}
[17:43:13] [LOG] /Mixer/CrossfaderPosition => {"type":0,"value":0.25999999046325684}
[17:43:13] [LOG] /Engine/Deck1/Play => {"state":false,"type":1}
[17:43:13] [LOG] /Engine/Deck1/PlayState => {"state":false,"type":1}
[17:43:13] [LOG] /Engine/Deck1/PlayStatePath => {"string":"/Engine/Deck1/Track/PlayPauseLEDState","type":8}
[17:43:13] [LOG] /Engine/Deck1/Track/ArtistName => {"string":"","type":8}
[17:43:13] [LOG] /Engine/Deck1/Track/TrackNetworkPath => {"string":"","type":8}
[17:43:13] [LOG] /Engine/Deck1/Track/SongLoaded => {"state":false,"type":1}
[17:43:13] [LOG] /Engine/Deck1/Track/SongName => {"string":"","type":8}
[17:43:13] [LOG] /Engine/Deck1/Track/TrackData => {"state":false,"type":3}
[17:43:13] [LOG] /Engine/Deck1/Track/TrackName => {"string":"","type":8}
[17:43:13] [LOG] /Engine/Deck1/CurrentBPM => {"type":0,"value":118.51221466064453}
[17:43:13] [LOG] /Engine/Deck1/ExternalMixerVolume => {"type":0,"value":0}
[17:43:13] [LOG] /Engine/Deck2/Play => {"state":false,"type":1}
[17:43:13] [LOG] /Engine/Deck2/PlayState => {"state":false,"type":1}
[17:43:13] [LOG] /Engine/Deck2/PlayStatePath => {"string":"/Engine/Deck2/Track/PlayPauseLEDState","type":8}
[17:43:13] [LOG] /Engine/Deck2/Track/ArtistName => {"string":"","type":8}
[17:43:13] [LOG] /Engine/Deck2/Track/TrackNetworkPath => {"string":"","type":8}
[17:43:13] [LOG] /Engine/Deck2/Track/SongLoaded => {"state":false,"type":1}
[17:43:13] [LOG] /Engine/Deck2/Track/SongName => {"string":"","type":8}
[17:43:13] [LOG] /Engine/Deck2/Track/TrackData => {"state":false,"type":3}
[17:43:13] [LOG] /Engine/Deck2/Track/TrackName => {"string":"","type":8}
[17:43:13] [LOG] /Engine/Deck2/CurrentBPM => {"type":0,"value":120}
[17:43:13] [LOG] /Engine/Deck2/ExternalMixerVolume => {"type":0,"value":0.05379859730601311}
[17:43:13] [LOG] TCP connection to 'primego:44819' local port: 10770
[17:43:13] [INFO] Connected to service 'FileTransfer' at port 44819
[17:43:18] [LOG] /Engine/Deck1/Track/TrackNetworkPath => -1
[17:43:18] [LOG] /Engine/Deck1/Track/TrackUri => -1
[17:43:18] [LOG] /Engine/Deck1/Track/TrackLength => -1
[17:43:18] [LOG] /Engine/Deck1/Track/TrackBytes => -1
[17:43:18] [LOG] /Engine/Deck1/Track/CurrentLoopInPosition => -1
[17:43:18] [LOG] /Engine/Deck1/Track/CurrentLoopOutPosition => -1
[17:43:18] [LOG] /Engine/Deck1/Track/LoopEnableState => -1
[17:43:18] [LOG] /Engine/Deck1/Track/SongAnalyzed => -1
[17:43:18] [LOG] /Engine/Deck1/Track/KeyLock => -1
[17:43:18] [LOG] /Engine/Deck1/Track/CurrentKeyIndex => -1
[17:43:18] [LOG] /Engine/Deck1/Track/Bleep => -1
[17:43:18] [LOG] /Engine/Deck1/CurrentBPM => -1
[17:43:18] [LOG] /Engine/Deck1/PlayState => -1
[17:43:18] [LOG] /Engine/Deck1/Pads/View => -1
[17:43:18] [LOG] /Engine/Deck1/SyncMode => -1
[17:43:18] [LOG] /Engine/Deck2/Track/TrackNetworkPath => -1
[17:43:18] [LOG] /Engine/Deck2/Track/TrackUri => -1
[17:43:18] [LOG] /Engine/Deck2/Track/TrackLength => -1
[17:43:18] [LOG] /Engine/Deck2/Track/TrackBytes => -1
[17:43:18] [LOG] /Engine/Deck2/Track/CurrentLoopInPosition => -1
[17:43:18] [LOG] /Engine/Deck2/Track/CurrentLoopOutPosition => -1
[17:43:18] [LOG] /Engine/Deck2/Track/LoopEnableState => -1
[17:43:18] [LOG] /Engine/Deck2/Track/SongAnalyzed => -1
[17:43:18] [LOG] /Engine/Deck2/Track/KeyLock => -1
[17:43:18] [LOG] /Engine/Deck2/Track/CurrentKeyIndex => -1
[17:43:18] [LOG] /Engine/Deck2/Track/Bleep => -1
[17:43:18] [LOG] /Engine/Deck2/CurrentBPM => -1
[17:43:18] [LOG] /Engine/Deck2/PlayState => -1
[17:43:18] [LOG] /Engine/Deck2/Pads/View => -1
[17:43:18] [LOG] /Engine/Deck2/SyncMode => -1
[17:43:18] [LOG] /Engine/Deck3/Track/TrackNetworkPath => -1
[17:43:18] [LOG] /Engine/Deck3/Track/TrackUri => -1
[17:43:18] [LOG] /Engine/Deck3/Track/TrackLength => -1
[17:43:18] [LOG] /Engine/Deck3/Track/TrackBytes => -1
[17:43:18] [LOG] /Engine/Deck3/Track/CurrentLoopInPosition => -1
[17:43:18] [LOG] /Engine/Deck3/Track/CurrentLoopOutPosition => -1
[17:43:18] [LOG] /Engine/Deck3/Track/LoopEnableState => -1
[17:43:18] [LOG] /Engine/Deck3/Track/SongAnalyzed => -1
[17:43:18] [LOG] /Engine/Deck3/Track/KeyLock => -1
[17:43:18] [LOG] /Engine/Deck3/Track/CurrentKeyIndex => -1
[17:43:18] [LOG] /Engine/Deck3/Track/Bleep => -1
[17:43:18] [LOG] /Engine/Deck3/CurrentBPM => -1
[17:43:18] [LOG] /Engine/Deck3/PlayState => -1
[17:43:18] [LOG] /Engine/Deck3/Pads/View => -1
[17:43:18] [LOG] /Engine/Deck3/SyncMode => -1
[17:43:18] [LOG] /Engine/Deck4/Track/TrackNetworkPath => -1
[17:43:18] [LOG] /Engine/Deck4/Track/TrackUri => -1
[17:43:18] [LOG] /Engine/Deck4/Track/TrackLength => -1
[17:43:18] [LOG] /Engine/Deck4/Track/TrackBytes => -1
[17:43:18] [LOG] /Engine/Deck4/Track/CurrentLoopInPosition => -1
[17:43:18] [LOG] /Engine/Deck4/Track/CurrentLoopOutPosition => -1
[17:43:18] [LOG] /Engine/Deck4/Track/LoopEnableState => -1
[17:43:18] [LOG] /Engine/Deck4/Track/SongAnalyzed => -1
[17:43:18] [LOG] /Engine/Deck4/Track/KeyLock => -1
[17:43:18] [LOG] /Engine/Deck4/Track/CurrentKeyIndex => -1
[17:43:18] [LOG] /Engine/Deck4/Track/Bleep => -1
[17:43:18] [LOG] /Engine/Deck4/CurrentBPM => -1
[17:43:18] [LOG] /Engine/Deck4/PlayState => -1
[17:43:18] [LOG] /Engine/Deck4/Pads/View => -1
[17:43:18] [LOG] /Engine/Deck4/SyncMode => -1
[17:43:18] [LOG] /Engine/DeckCount => -1
[17:43:18] [LOG] /GUI/Decks/Deck/ActiveDeck => -1
[17:43:18] [LOG] /Client/Librarian/DevicesController/CurrentDevice => -1
[17:43:18] [LOG] /Client/Preferences/Profile/Application/SyncMode => -1

@MarByteBeep
Copy link

@icedream would it be possible to send such a network trace for fetching a simple image (album art for instance)? Unfortunately I don't have any devices here other than the Denon Prime Go, so it's impossible for me to make such a trace myself.

@icedream
Copy link
Owner Author

If I can make it happen in Resolume Arena I can try and trigger such a download and record it later... not sure if it even displays album art at all, so I would have to look at that.

@icedream
Copy link
Owner Author

icedream commented Oct 12, 2021

Also I thought I replied to this a long while ago already, strange. Anyways,

  • When the StateMap services received an update, it starts with a smaa magic marker. When this marker is followed by 0x0000 0000, I expect a name + JSON. But when it is followed by 0x0000 07d2, I get a name + uint32. This happened in my first example. In your code you named this uint32 interval. Did you see any other values other than 0x0 and 0xFFFF FFFF?

  • Coincidentally that 0x0000 07d2 pattern can also be observed in those FileTransfer responses...

Interval was only my best guess at what that field is for, as I thought it was different between SoundSwitch and Resolume Arena and may coincide with how often they polled/received updates. I can't verify my thoughts on this anymore and the probability of that being anywhere close to true is low enough that I would rather ignore that. 😅

Also I thought I had a case where that number was 0x17 or 0x1d instead but I couldn't find it in any of the network recordings I still have. Consider it guesswork.

@MarByteBeep
Copy link

MarByteBeep commented Oct 12, 2021

Thanks for your reply! That changing number seems to be indeed something time based. It increased every so many seconds.

Another thing I observed is when sending

Offset   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
000000   00 00 00 10 66 6C 74 78 00 00 00 00 00 00 07 D2   ....fltx.......Ò
000010   00 00 00 00  

I always seem to get the location of the currently available sources (in my case I have 2 sources (indicated by the 0x02 that follows the 0x03). They simply append the names of the sources after each other):

Offset   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
000000   00 00 00 00 00 00 00 03 00 00 00 02 00 00 00 14   ................
000010   00 4D 00 75 00 73 00 69 00 63 00 20 00 28 00 53   .M.u.s.i.c. .(.S
000020   00 44 00 29 00 00 00 14 00 4D 00 55 00 53 00 49   .D.).....M.U.S.I
000030   00 43 00 41 00 20 00 42 00 49 00 47 01 01 01      .C.A. .B.I.G...

After the last name they append 3 times 0x01, marking some end?

Then in a separate message they send the following:

Offset   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
000000   00 00 00 00 00 00 00 02 01 00 00 00 00 7F FF FF   ..............ÿÿ
000010   FF FF FF FF FF                                    ÿÿÿÿÿ

(also some kind of end marker?)

So those last two uint32s: 0x7d2 + 0x0, represent some kind of a special code that sends me the location. If I change it to: 0x07d2 + 0x1, I get nothing. Or when changing it to 0x7d1 + 0x0, I get garbage.

Interesting :)

EDIT: I'll update this message when I find more info

@MarByteBeep
Copy link

MarByteBeep commented Oct 13, 2021

Hi @icedream,

I managed to take the FileSystem service a few steps further. WIP code can be found here:

https://github.com/MarByteBeep/StageLinq/blob/main/services/FileTransfer.ts

When changing main.ts to:

const ftx = await controller.connectToService(FileTransfer);
const sources = await ftx.requestSources();
console.log(sources);

I get the following output:

 {
    source: 'Music (SD)',
    database: { location: '/Music (SD)/Engine Library/m.db', size: 18006016 }
  }

It's a start :)

Up next, requesting that file. I noticed that when sending a file transfer request, we need to send a starting chunk id and an ending chunk id. Then the device will send back those chunks.

I'll let you know when I get that working.

EDIT: I noticed that all Album Art is stored as blobs in that database (SQLite). So once we're able to download the library database, it'll be quite easy to match the album art with the currently active songs.

@icedream
Copy link
Owner Author

icedream commented Oct 13, 2021

The capture I posted at the beginning of the issue involved downloading an SQLite database at least, so maybe that helps? I had to cut some of the data off since it's of course way too big.

Awesome progress so far!

@MarByteBeep
Copy link

Yes, that capture really helps. I think I now more or less figured out on how to retrieve that database myself. I think later today I'll be able to share it with you!

@MarByteBeep
Copy link

Hi @icedream FileTransfer is now able to retrieve the source database and display its number of album arts using SQLite. I pushed the changes. From here it is easy to get the album art binary data (is a blob in the DB), so we can display that for instance when a song is playing. Perhaps you can test it in Go as well using your equipment?

@mhite
Copy link
Contributor

mhite commented Oct 1, 2022

I think Prime supports audio portion of mp4 playback, right? Do any VJ stagelinq integrations today automatically download the currently playing track (assuming it is mp4) and playback a timeline (via Beatinfo) sync'ed video output of the same file (as downloaded on demand)? I can't remember what software product video demo I looked at, but it seemed the VJ side had to drag their own copy of the video on to the timeline. Seems like that manual process could be avoided if you can just D/L a track from a player on demand (and it is an mp4).

Just thinking out loud! Very ignorant on how existing Stagelinq integrations work so excuse my silly questions.

@icedream
Copy link
Owner Author

icedream commented Oct 2, 2022

I think you're right, any video synchronization is basically just done through positions from what I know and avoid downloading video data off the players – DJs bringing along their own video for playback to any event simply isn't a common thing after all. 😄

Since video files can be pretty big, I wonder about performance (CPU or I/O load) on the devices for both decoding the audio part and transmitting the whole file over the network while simultaneously playing it back. Sounds extreme, but at least the Prime 4 may be able to deal with it just fine…?

@mhite
Copy link
Contributor

mhite commented Oct 2, 2022

I think you're right, any video synchronization is basically just done through positions from what I know and avoid downloading video data off the players – DJs bringing along their own video for playback to any event simply isn't a common thing after all. 😄

Since video files can be pretty big, I wonder about performance (CPU or I/O load) on the devices for both decoding the audio part and transmitting the whole file over the network while simultaneously playing it back. Sounds extreme, but at least the Prime 4 may be able to deal with it just fine…?

I am just daydreaming on the possibilities if CPU and I/O were not a barrier. 🙃 I guess you could "pre-sync" data between DJ and VJ by just exporting the same library to two different USB sticks using Engine DJ. At that point, you can try to do something intelligent to select the right file on the VJ side based upon Stagelinq track metadata. It would be interesting if there was a VJ software that tried to intelligently and intentionally do this to bring a simple VJ experience to Denon Prime.

Also, I know very little about the VJ software market. But when I saw the manual file fiddling in the demo video (maybe it was Resolume?) I thought... ugh, what a terrible workflow.

@mhite
Copy link
Contributor

mhite commented Oct 6, 2022

@icedream - Any thoughts or musings on how to implement this in the go-stagelinq framework? Since this is a request/response type service that seems to have multiple types of requests that can be sent (ie. filestat, getsources, getchunks, etc.) and response types that can be received (corresponding to requests) on the same TCP socket, how can we best orchestrate this on a hypothetical FileTransferConnection? Should there be a mutex lock on the struct so we can ensure only one request/response is being handled at a time? In other words, each method that performs a different request command takes the lock. (Or does that really belong on the messageConnection? Hoping not.) Also wondering if we should continue to use a goroutine to read off the tcp socket and dispatch various messages types to different response type channels? Sorry if this sounds confusing, but really trying to wrap my head on how to best approach this and not stray off into a bad approach.

@icedream
Copy link
Owner Author

icedream commented Oct 7, 2022

Depending on how we want to abstract it, it may be enough to have one method sending and then receiving messages, expecting certain types. In that case we would want a mutex, too, for methods to not step on each other.

If you want to deal with file transfer asynchronously, as in multiple parallel requests are possible, then you basically got something that requires output channels and a goroutine to sort the messages to the correct output channels. I don't know if you already know whether the protocol allows for this to happen. Then the pattern above for things that are not asynchronous streams (file stat sounds like that) would not directly apply to the messageConnection, but to messages filtered out to not belong to any other output channel.

The messageConnection itself should have no logic regarding the meaning of the messages, it just deals with the basic transfer of messages without further context, so no worries about that. 😄

@mhite
Copy link
Contributor

mhite commented Oct 9, 2022

  • Coincidentally that 0x0000 07d2 pattern can also be observed in those FileTransfer responses...

@MarByteBeep - I think this might be the Denon Stagelinq device initiating a "GetSources" request (0x07d2) over the established connection towards us (the client). I think the first 4 bytes represent a request ID which we would include in our response. I think it might just want to keep sending this, with an incrementing request ID (?), until we actually respond or hangup.

@mhite
Copy link
Contributor

mhite commented Oct 9, 2022

Another interesting bit -- I think there is another message ID of 9 which represents "unknown/malformed request." Not 100% sure.

@mhite
Copy link
Contributor

mhite commented Oct 9, 2022

So...

stat - 0x7d1
get sources - 0x7d2
request file transfer - 0x7d4
request chunk - 0x7d5
transfer complete - 0x7d6

seems like 0x7d3 should be something :)

I tried a few payloads thinking perhaps it was a way to issue an ls / dir type command... couldn't get anything other than what I think is an error message back (message id of 9).

@mhite
Copy link
Contributor

mhite commented Oct 9, 2022

These "Unknown" 8 messages .... dying to know what the heck they are!

payload:

00 00 00 12 66 6c 74 78 00 00 00 00 00 00   ......fltx......
00 08 00 00 00 02 00 31 

4 bytes - uint big endian length - 00 00 00 12
4 bytes - magic fltx - 66 6c 74 78
4 bytes - uint big endian request id - 00 00 00 00
4 bytes - uint big endian message code - 00 00 00 08
4 bytes - uint big endian number of records following - 00 00 00 02
1 byte records, repeating twice... ?

@honusz
Copy link

honusz commented Oct 9, 2022

These "Unknown" 8 messages .... dying to know what the heck they are!

@mhite Not sure if it's helpful, but I can tell you that I get a different response from the OfflineAnalyzers compared to the players. Their Message8 only has 4 bytes after code, not 6.
The players' main FLTX endpoints, whether or not they have media connected, are both 6 bytes. I can't remember off the top of my head if they're the same (whether they have media or not).

@mhite
Copy link
Contributor

mhite commented Oct 9, 2022

@mhite Not sure if it's helpful, but I can tell you that I get a different response from the OfflineAnalyzers compared to the players. Their Message8 only has 4 bytes after code, not 6. The players' main FLTX endpoints, whether or not they have media connected, are both 6 bytes. I can't remember off the top of my head if they're the same (whether they have media or not).

@honusz - Oh, I see what you mean! It's 4 bytes of 0xFF for me on the OfflineAnalyzers. (This is on a Prime4, btw.) So much for that theory on it being a length or record count!

@mhite
Copy link
Contributor

mhite commented Oct 9, 2022

@icedream - Thanks for the guidance. I've got the very rough beginnings of something. There's a lot to unwind with FileTransfer!

@mhite
Copy link
Contributor

mhite commented Oct 9, 2022

  • Coincidentally that 0x0000 07d2 pattern can also be observed in those FileTransfer responses...

@MarByteBeep - I think this might be the Denon Stagelinq device initiating a "GetSources" request (0x07d2) over the established connection towards us (the client). I think the first 4 bytes represent a request ID which we would include in our response. I think it might just want to keep sending this, with an incrementing request ID (?), until we actually respond or hangup.

Now this has me thinking that if we are able to respond properly that we could actually show up as a source on the Denon gear and serve up a remote file system. Crazy talk?

@mhite
Copy link
Contributor

mhite commented Oct 16, 2022

Have built a very bare bones go-stagelinq implementation but still struggling to get anything back but an error (which I think might be message id 9) when I try to retrieve a chunk for a file transfer id. Also, when I try this with my Prime4, I don't ever see this mysterious Timecode message in any of my interactions. This one is tough!

... and literally after typing this in, I tested a new hypothesis as to what the problem might be, and managed to get a response. Lol! New discoveries and details to come...

@mhite
Copy link
Contributor

mhite commented Oct 16, 2022

Ok, some background:

  • The go-stagelinq library very much treats messages read and message writes as distinct and decoupled from each other. Currently it's doesn't try match specific requests (write) with a specific network response (read). (In its reference implementation for message handling, it directs all messages of a specific type down a channel.)
  • I've got a strawman implementation that allows the requestor to create a channel for responses to specific requests written to the network
  • I'm doing this today by creating a "request ID" map of channels for each type of message and putting this in my FileTransferConnection struct. I've got a mutex so that we can read/write to this safely in concurrently running goroutines.
  • Here's the important detail: the request ID is put in the packet right after the fltx magic bytes. Discussion I've seen thus far amongst the stagelinq reversing code and community assumes that it is just an uint32 0x0 pad. If you use this as a transaction/request-id, it allows you to inject a state into the request and get it reflected back in the response by the Denon stagelinq responder. Why is this useful? In go-stagelinq, everything is async; for a protocol like FileTransfer, we need some way of stitching the right response back to the requestor go routine. The implementation doesn't write a request and block on a waiting for its response. We move on and handle any sort of message that might arrive on the wire, whether it is the response to our last request or something else entirely.
  • The other important bit is that this "request/transaction ID" needs to the same when you request a transfer ID and then perform a subsequent chunk request for that transfer ID. I made them different, and I was getting an error. Finally I made them the same, and it worked!
  • Once it's not in such disarray/trashfire state, I'll push a working example it to a remote branch in case anyone is curious.

@mhite
Copy link
Contributor

mhite commented Oct 17, 2022

With regards to the sources response message (message id 0x3), this is kind of interesting:

In @icedream's capture, it has an example response with 0x1, 0x1, 0x0 at end:

    00000000  00 00 00 13 66 6c 74 78  00 00 00 02 00 00 00 03   ....fltx ........
    00000010  00 00 00 00 01 01 00   

...but another with 0x1, 0x1, 0x1:

0000005C  00 00 00 65 66 6c 74 78  00 00 00 65 00 00 00 03   ...efltx ...e....
0000006C  00 00 00 02 00 00 00 26  00 49 00 63 00 65 00 64   .......& .I.c.e.d
0000007C  00 72 00 65 00 61 00 6d  00 53 00 53 00 44 00 20   .r.e.a.m .S.S.D. 
0000008C  00 28 00 55 00 53 00 42  00 20 00 31 00 29 00 00   .(.U.S.B . .1.)..
0000009C  00 24 00 41 00 4e 00 41  00 52 00 43 00 48 00 59   .$.A.N.A .R.C.H.Y
000000AC  00 55 00 53 00 42 00 20  00 28 00 55 00 53 00 42   .U.S.B.  .(.U.S.B
000000BC  00 20 00 31 00 29 01 01  01 00 00 00 19 66 6c 74   . .1.).. .....flt
000000CC  78 00 00 00 65 00 00 00  02 01 00 00 00 00 7f ff   x...e... ........
000000DC  ff ff ff ff ff ff  

As another data point, it also looks like @MarByteBeep 's implementation always expects 0x1, 0x1, 0x1:

https://github.com/MarByteBeep/StageLinq/blob/main/services/FileTransfer.ts#L73

The only thing that jumps out is that the response in @icedream 's capture with 0x1, 0x1, 0x0 is a response with 0 sources.

Although in my own capture of a Prime4 OfflineAnalyzer FIleTransfer service, a response with 0 sources can also have 0x1, 0x1, 0x1 at the end of a response:

0000   b0 f1 d8 31 7b e9 00 05 95 01 91 be 08 00 45 02   ...1{.........E.
0010   00 7c 94 76 40 00 40 06 9a 65 c0 a8 44 c6 c0 a8   .|.v@[email protected]...
0020   45 87 b3 17 f3 a1 1d 08 3e 80 99 83 2e 73 80 18   E.......>....s..
0030   01 fd 39 04 00 00 01 01 08 0a b0 1a e6 29 53 6e   ..9..........)Sn
0040   44 0e 00 00 00 10 66 6c 74 78 00 00 00 00 00 00   D.....fltx......
0050   00 08 ff ff ff ff 00 00 00 13 66 6c 74 78 00 00   ..........fltx..
0060   00 00 00 00 00 03 00 00 00 00 01 01 01 00 00 00   ................
0070   19 66 6c 74 78 00 00 00 00 00 00 00 02 01 00 00   .fltx...........
0080   00 00 7f ff ff ff ff ff ff ff                     ..........

@honusz
Copy link

honusz commented Oct 17, 2022

The only thing that jumps out is that the response in @icedream 's capture with 0x1, 0x1, 0x0 is a response with 0 sources. Although in my own capture of a Prime4 OfflineAnalyzer FIleTransfer service, a response with 0 sources can also have 0x1, 0x1, 0x1 at the end of a response:

I've only ever seen 0x1, 0x1, 0x1 from my SC6ks.
Perhaps there's a different response for internal SSD? Or maybe SD card, though I don't think so.

That being said, MarByte's implementation (with its triple 0x1 assertion) has been used in the wild quite a bit. I feel like we likely would have caught any messages that broke this pattern.

@honusz
Copy link

honusz commented Oct 17, 2022

Here's the important detail: the request ID is put in the packet right after the fltx magic bytes. Discussion I've seen thus far amongst the stagelinq reversing code and community assumes that it is just an uint32 0x0 pad. If you use this as a transaction/request-id, it allows you to inject a state into the request and get it reflected back in the response by the Denon stagelinq responder. Why is this useful? In go-stagelinq, everything is async; for a protocol like FileTransfer, we need some way of stitching the right response back to the requestor go routine. The implementation doesn't write a request and block on a waiting for its response. We move on and handle any sort of message that might arrive on the wire, whether it is the response to our last request or something else entirely.

Yeah, I've noticed this. I suspect it's used to have handle multiple transfers.
In MarByte, what is treated as the transfer ID is always 0x1, which I believe is something else. The true transfer ID is, as you say, in that first 4 byte field after 'fltx'.
Maybe this allows concurrent transfers (MarByte doesn't presently allow concurrency), but it may just allow transfer sessions to be cached.
In some of my captures between players, the device returns to previous transfer IDs to retrieve new data, such as when an updated DB state is sent by Broadcast.
Perhaps the device keeps a record of files and their respective transfer ids.

I can send you the capture I'm referring to. It's complex (multiple filetransfer services due to offline analyzers), but contains a lot of info that may be helpful in your endeavours.

I don't want to post it publicly out of respect to the artists as it contains the data for purchase tracks, but if you give a contact (email, twitter, discord) I'll hook you up.

@mhite
Copy link
Contributor

mhite commented Oct 18, 2022

The only thing that jumps out is that the response in @icedream 's capture with 0x1, 0x1, 0x0 is a response with 0 sources. Although in my own capture of a Prime4 OfflineAnalyzer FIleTransfer service, a response with 0 sources can also have 0x1, 0x1, 0x1 at the end of a response:

I've only ever seen 0x1, 0x1, 0x1 from my SC6ks. Perhaps there's a different response for internal SSD? Or maybe SD card, though I don't think so.

That being said, MarByte's implementation (with its triple 0x1 assertion) has been used in the wild quite a bit. I feel like we likely would have caught any messages that broke this pattern.

Yeah, I think @icedream took that capture from Resolume Arena to a Prime 4. So perhaps this might be something we see for certain classes of clients like Resolume. It also makes me think think these values are probably booleans of some sort. And just to toss out untested hypotheticals, I wonder if it also is some of signal to say "yeah don't ask me for my sources again because I will always have none."

It is good to know that we can expect 0x1 0x1 0x1 when dealing with players that we've observed so far!

@mhite
Copy link
Contributor

mhite commented May 2, 2023

I've been trying to craft a response to the List command we receive from the JC11 to get it to stop asking. Haven't figured that out yet unfortunately.

@honusz
Copy link

honusz commented May 2, 2023

7f ff ff ff ff ff ff ff is 127.255.255.255, the broadcast address on the localhost network 127.0.0.0/8.

Interesting. I always assumed these were flags (7F as half-state between 00 and FF), but I suppose it definitely could be the broadcast address.
I'm trying to recall when these messages are received, it's just the 0x2 messages yeah?

I've been trying to craft a response to the List command we receive from the JC11 to get it to stop asking. Haven't figured that out yet unfortunately.

It's basically just the player sending the standard first inquiry players send to one another.
Basically just tell it you have no sources and it knocks it off.

ctx.writeUInt32(message.txid);
ctx.writeUInt32(0x3);
ctx.writeUInt32(0x0);
ctx.writeUInt16(257);
ctx.writeUInt8(0x0);

@mhite
Copy link
Contributor

mhite commented May 2, 2023

7f ff ff ff ff ff ff ff is 127.255.255.255, the broadcast address on the localhost network 127.0.0.0/8.

Interesting. I always assumed these were flags (7F as half-state between 00 and FF), but I suppose it definitely could be the broadcast address. I'm trying to recall when these messages are received, it's just the 0x2 messages yeah?

Yes, that's what I was looking at. Not sure if we see the pattern in other stuff, though.

I've been trying to craft a response to the List command we receive from the JC11 to get it to stop asking. Haven't figured that out yet unfortunately.

It's basically just the player sending the standard first inquiry players send to one another. Basically just tell it you have no sources and it knocks it off.

ctx.writeUInt32(message.txid);
ctx.writeUInt32(0x3);
ctx.writeUInt32(0x0);
ctx.writeUInt16(257);
ctx.writeUInt8(0x0);

Hmm. I'm sending that with no luck.

0000   00 00 00 13 66 6c 74 78 00 00 00 00 00 00 00 03   ....fltx........
0010   00 00 00 00 01 01 00                              .......

@honusz
Copy link

honusz commented May 2, 2023

Hmm. I'm sending that with no luck.

0000   00 00 00 13 66 6c 74 78 00 00 00 00 00 00 00 03   ....fltx........
0010   00 00 00 00 01 01 00                              .......

Are you including the txid from the request message the device sent?

@mhite
Copy link
Contributor

mhite commented May 2, 2023

Are you including the txid from the request message the device sent?

Yeah, I reflect the txid in my response.

@honusz
Copy link

honusz commented May 2, 2023

Are you including the txid from the request message the device sent?

Yeah, I reflect the txid in my response.

Only asking because the wireshark packet you quoted has 00 for txid

@honusz
Copy link

honusz commented May 2, 2023

ctx.writeFixedSizedString(MAGIC_MARKER);

ctx.writeUInt32(message.txid);

ctx.writeUInt32(0x3);

ctx.writeUInt32(0x0);

ctx.writeUInt16(257);

ctx.writeUInt8(0x0);

I should clarify that the last two fields are this first-last-dir flags, obviously I wrote this before you discovered their meaning, lol

So I guess that last field should be 0x1, but it doesn't seem to matter.

It's weird, it definitely works for me; once I send that message, no more RequestSources / Dir messages from the unit.
message.txid is the txId I received in the 0x7d2 message from the unit.

@mhite
Copy link
Contributor

mhite commented May 2, 2023

Ok, I went back and crawled through the code that handles the initial message parsing of the received message. There's a problem in my parser for fileTransferListRequestMessage and it wasn't correctly obtaining the transaction ID. Thanks for pushing me in the right direction!

@honusz
Copy link

honusz commented May 2, 2023

No prob!
This protocol is quite frustrating to parse. Like, would it be too much to ask for a delimiter between fields? Considering they use size-prefixed strings, it's just a bit of a pain.

I take back what I said about protobufs, that would make this a lot easier

@mhite
Copy link
Contributor

mhite commented May 2, 2023

Ok, so even after I fixed the txid reflection issue, it was still giving me grief.

What I did discover is that if I send the 0x2 command after the 0x3 (sources/list) response I can get it to shut up. You aren't doing this? 😕

> announce start
Starting announcement every 1s...
> discover 3
Performing discovery for 3s...
2023/05/02 16:28:41 discovered: 192.168.68.198 "prime4" "OfflineAnalyzer" "1.0.0"
2023/05/02 16:28:41 discovered: 192.168.68.198 "prime4" "OfflineAnalyzer" "1.0.0"
2023/05/02 16:28:41 discovered: 192.168.68.198 "prime4" "JC11" "3.0.1"
 > connect 3
Connecting...
2023/05/02 16:28:47 filetransfer service advertised on port 34421
Connection successful.
> 2023/05/02 16:28:47 RECV fileTransferUnknown8Message: (*stagelinq.fileTransferUnknown8Message)(0x14000288000)({
 TransactionId: (uint32) 0,
 Blob: ([]uint8) (len=18 cap=18) {
  00000000  66 6c 74 78 00 00 00 00  00 00 00 08 00 00 00 02  |fltx............|
  00000010  00 31                                             |.1|
 }
})
2023/05/02 16:28:47 RECV fileTransferListRequestMessage: (*stagelinq.fileTransferListRequestMessage)(0x1400028e150)({
 TransactionId: (uint32) 53,
 Path: (string) "",
 Blob: ([]uint8) (len=16 cap=16) {
  00000000  66 6c 74 78 00 00 00 35  00 00 07 d2 00 00 00 00  |fltx...5........|
 }
})
2023/05/02 16:28:47 response = (stagelinq.fileTransferListResponseMessage) {
 TransactionId: (uint32) 53,
 Items: ([]string) (len=1 cap=1) {
  (string) (len=7) "cowpoop"
 },
 First: (bool) true,
 Last: (bool) true,
 Directory: (bool) true
}

2023/05/02 16:28:47 response2 = (stagelinq.fileTransferFrameEndMessage) {
 TransactionId: (uint32) 53,
 UnknownFlag: (bool) false,
 Blob: ([]uint8) {
 }
}

If I flip that "UnknownFlag" boolean in the fileTransferFrameEndMessage you see above, the Denon will try to crawl the "cowpoop" directory I said existed in the 0x3 response above it.

For example:

> announce start
Starting announcement every 1s...
> discover 3
Performing discovery for 3s...
2023/05/02 16:32:49 discovered: 192.168.68.198 "prime4" "OfflineAnalyzer" "1.0.0"
2023/05/02 16:32:49 discovered: 192.168.68.198 "prime4" "OfflineAnalyzer" "1.0.0"
2023/05/02 16:32:49 discovered: 192.168.68.198 "prime4" "JC11" "3.0.1"
> connect 3
Connecting...
2023/05/02 16:32:55 filetransfer service advertised on port 34421
Connection successful.
> 2023/05/02 16:32:55 RECV fileTransferUnknown8Message: (*stagelinq.fileTransferUnknown8Message)(0x1400006e300)({
 TransactionId: (uint32) 0,
 Blob: ([]uint8) (len=18 cap=18) {
  00000000  66 6c 74 78 00 00 00 00  00 00 00 08 00 00 00 02  |fltx............|
  00000010  00 31                                             |.1|
 }
})
2023/05/02 16:32:55 RECV fileTransferListRequestMessage: (*stagelinq.fileTransferListRequestMessage)(0x14000230870)({
 TransactionId: (uint32) 54,
 Path: (string) "",
 Blob: ([]uint8) (len=16 cap=16) {
  00000000  66 6c 74 78 00 00 00 36  00 00 07 d2 00 00 00 00  |fltx...6........|
 }
})
2023/05/02 16:32:55 response = (stagelinq.fileTransferListResponseMessage) {
 TransactionId: (uint32) 54,
 Items: ([]string) (len=1 cap=1) {
  (string) (len=7) "cowpoop"
 },
 First: (bool) true,
 Last: (bool) true,
 Directory: (bool) true
}

2023/05/02 16:32:55 response2 = (stagelinq.fileTransferFrameEndMessage) {
 TransactionId: (uint32) 54,
 UnknownFlag: (bool) true,
 Blob: ([]uint8) {
 }
}

2023/05/02 16:32:55 RECV fileTransferListRequestMessage: (*stagelinq.fileTransferListRequestMessage)(0x14000230ba0)({
 TransactionId: (uint32) 55,
 Path: (string) (len=8) "/cowpoop",
 Blob: ([]uint8) (len=32 cap=32) {
  00000000  66 6c 74 78 00 00 00 37  00 00 07 d2 00 00 00 10  |fltx...7........|
  00000010  00 2f 00 63 00 6f 00 77  00 70 00 6f 00 6f 00 70  |./.c.o.w.p.o.o.p|
 }
})
2023/05/02 16:32:55 response = (stagelinq.fileTransferListResponseMessage) {
 TransactionId: (uint32) 55,
 Items: ([]string) (len=1 cap=1) {
  (string) (len=7) "cowpoop"
 },
 First: (bool) true,
 Last: (bool) true,
 Directory: (bool) true
}

2023/05/02 16:32:55 response2 = (stagelinq.fileTransferFrameEndMessage) {
 TransactionId: (uint32) 55,
 UnknownFlag: (bool) true,
 Blob: ([]uint8) {
 }
}

It stops after the follow-up request for "/cowpoop". You can see I just synthetically generate only a "cowpoop" response. It is probably looking for an "Engine Library" directory would be my guess? I will have to code up a full-fledged response handler to traverse a file system to play with this more. That will take some time, but it's on my list of things to play with. I have a hunch we can get a remote computer to show up as a source on the Denon. (I haven't tried doing any packet captures on how Engine DJ exposes this today given it provides new functionality like this...)

BTW, that UnknownFlag is just the first byte after the 0x2 command uint32. I figure it is a boolean representing "Success" or something like that since it is 1 when things are good and 0 when 0x2 gets returned on say the ls of a non-existent path or ejected volume.

@mhite
Copy link
Contributor

mhite commented May 2, 2023

Ok, wow, actually I just walked over to the Denon to shut it off and take a walk... but look what I see now... 😁 Notice my fake cowpoop in the UI now.

IMG_0171

@mhite
Copy link
Contributor

mhite commented May 2, 2023

Lol, it's a new rabbit hole! 🐰

@honusz
Copy link

honusz commented May 3, 2023

Lol, it's a new rabbit hole! 🐰

Hahaha, start serving it tunes!!

@honusz
Copy link

honusz commented May 3, 2023

What I did discover is that if I send the 0x2 command after the 0x3 (sources/list) response I can get it to shut up. You aren't doing this? 😕

You know what... they behaviour may actually be different, seeing as in my method the players initiate the connection to me.

@honusz
Copy link

honusz commented May 3, 2023

@mhite okay, I think it is different because you are the player initiating the connection. Helpfully, because I do it differently I get the EndOfMessage response back from the players.

The player with media attached sends back (after the Magic>TxId>MessageId preamble)<Buffer 01 00 00 77 55 7f ff ff ff ff ff ff ff>
The player with no media attached sends back <Buffer 00 00 00 00 00 7f ff ff ff ff ff ff ff>

@mhite
Copy link
Contributor

mhite commented May 3, 2023

@honusz - ah, very interesting. so you have a network listener waiting to receive connections initiated from standalone players?

@honusz
Copy link

honusz commented May 3, 2023

@honusz - ah, very interesting. so you have a network listener waiting to receive connections initiated from standalone players?

Indeed, it's the method I posted about a while ago here.

Somethings about it make it much easier - it's less of a state-headache to have a server with multiple clients re/connecting to it, rather than manage a client connected to multiple servers.

@mhite
Copy link
Contributor

mhite commented May 4, 2023

@honusz - Ah, now I remember! And your approach definitely makes sense.

Made some more progress -- I can now browse folders on my local hard drive via the Denon device. I wish I knew more about the unknown byte blob in the Stat command responses, though.

IMG_0174

IMG_0173

@mhite
Copy link
Contributor

mhite commented May 5, 2023

@honusz - have you ever spotted this 0x7d8?

0000   b0 f1 d8 31 7b e9 00 05 95 01 91 be 08 00 45 02   ...1{.........E.
0010   00 44 4d 33 40 00 40 06 e1 e0 c0 a8 44 c6 c0 a8   .DM3@[email protected]...
0020   45 87 a1 ef c7 9e 13 fe 49 02 85 12 29 2c 80 18   E.......I...),..
0030   01 fb 4e bd 00 00 01 01 08 0a 95 dc 29 fe a6 d7   ..N.........)...
0040   5b f9 00 00 00 0c 66 6c 74 78 00 00 00 0d 00 00   [.....fltx......
0050   07 d8                                             ..

It was sent in response to me sending back a transfer ID that was requested:

0000   00 05 95 01 91 be b0 f1 d8 31 7b e9 08 00 45 02   .........1{...E.
0010   00 50 00 00 40 00 40 06 2f 08 c0 a8 45 87 c0 a8   .P..@.@./...E...
0020   44 c6 c7 9e a1 ef 85 12 29 10 13 fe 49 02 80 18   D.......)...I...
0030   08 00 a0 3b 00 00 01 01 08 0a a6 d7 5b f9 95 dc   ...;........[...
0040   29 f8 00 00 00 18 66 6c 74 78 00 00 00 0d 00 00   ).....fltx......
0050   00 04 00 00 00 00 00 7a af df 00 00 00 01         .......z......

It never does request a chunk from me, so maybe it doesn't like my response. And perhaps x07d8 is telling me "didn't like that transfer id" or "didn't like that response". Not sure what's wrong with it, though.

@honusz
Copy link

honusz commented May 5, 2023

@honusz - have you ever spotted this 0x7d8?

It was sent in response to me sending back a transfer ID that was requested:

It never does request a chunk from me, so maybe it doesn't like my response. And perhaps x07d8 is telling me "didn't like that transfer id" or "didn't like that response". Not sure what's wrong with it, though.

I have seen 0x7d8 in my captures between the players.

Player1 Requests a Transfer Id:

0000   00 05 95 02 33 f9 00 05 95 02 34 0c 08 00 45 00   ....3.....4...E.
0010   01 08 74 70 40 00 40 06 3f 88 c0 a8 02 54 c0 a8   ..tp@.@.?....T..
0020   02 53 8d 3d 9a 7f bc b5 f4 85 1a 74 b3 d9 80 18   .S.=.......t....
0030   02 2b 2e 69 00 00 01 01 08 0a 0a 5f 95 d0 e4 14   .+.i......._....
0040   90 d2 00 00 00 d0 66 6c 74 78 00 00 00 53 00 00   ......fltx...S..
0050   07 d4 00 00 00 bc 00 2f 00 44 00 4a 00 32 00 20   ......./.D.J.2. 
0060   00 28 00 55 00 53 00 42 00 20 00 31 00 29 00 2f   .(.U.S.B. .1.)./
0070   00 45 00 6e 00 67 00 69 00 6e 00 65 00 20 00 4c   .E.n.g.i.n.e. .L
0080   00 69 00 62 00 72 00 61 00 72 00 79 00 2f 00 4d   .i.b.r.a.r.y./.M
0090   00 75 00 73 00 69 00 63 00 2f 00 5a 00 6f 00 6f   .u.s.i.c./.Z.o.o
00a0   00 20 00 42 00 72 00 61 00 7a 00 69 00 6c 00 2f   . .B.r.a.z.i.l./
00b0   00 54 00 68 00 65 00 20 00 4e 00 6f 00 72 00 74   .T.h.e. .N.o.r.t
00c0   00 68 00 2f 00 31 00 34 00 31 00 32 00 32 00 34   .h./.1.4.1.2.2.4
00d0   00 34 00 37 00 5f 00 54 00 68 00 65 00 5f 00 4e   .4.7._.T.h.e._.N
00e0   00 6f 00 72 00 74 00 68 00 5f 00 4f 00 72 00 69   .o.r.t.h._.O.r.i
00f0   00 67 00 69 00 6e 00 61 00 6c 00 5f 00 4d 00 69   .g.i.n.a.l._.M.i
0100   00 78 00 20 00 28 00 33 00 29 00 2e 00 6d 00 70   .x. .(.3.)...m.p
0110   00 33 00 00 00 00                                 .3....

Player2 replies with the 0x4 TransferId message:

0000   00 05 95 02 34 0c 00 05 95 02 33 f9 08 00 45 00   ....4.....3...E.
0010   00 50 ee 6a 40 00 40 06 c6 45 c0 a8 02 53 c0 a8   .P.j@[email protected]..
0020   02 54 9a 7f 8d 3d 1a 74 b3 d9 bc b5 f5 59 80 18   .T...=.t.....Y..
0030   12 e0 70 16 00 00 01 01 08 0a e4 14 90 d3 0a 5f   ..p............_
0040   95 d0 00 00 00 18 66 6c 74 78 00 00 00 53 00 00   ......fltx...S..
0050   00 04 00 00 00 00 00 e6 d4 3d 00 00 00 01         .........=....

Player1 sends some requests about another txid....

Player1 sends 0x7d8 message:

0000   00 05 95 02 33 f9 00 05 95 02 34 0c 08 00 45 00   ....3.....4...E.
0010   00 44 74 72 40 00 40 06 40 4a c0 a8 02 54 c0 a8   .Dtr@.@[email protected]..
0020   02 53 8d 3d 9a 7f bc b5 f5 81 1a 74 bf 45 80 18   .S.=.......t.E..
0030   02 20 42 a8 00 00 01 01 08 0a 0a 5f 95 d2 e4 14   . B........_....
0040   90 d4 00 00 00 0c 66 6c 74 78 00 00 00 53 00 00   ......fltx...S..
0050   07 d8                                             ..

Player1 sends some more requests about another txid....

Player1 returns to this txid, and sends a 0x7d5 request for chunks.

0000   00 05 95 02 33 f9 00 05 95 02 34 0c 08 00 45 00   ....3.....4...E.
0010   00 5c 74 74 40 00 40 06 40 30 c0 a8 02 54 c0 a8   .\tt@.@[email protected]..
0020   02 53 8d 3d 9a 7f bc b5 f5 a5 1a 74 c4 26 80 18   .S.=.......t.&..
0030   02 2b 3d 64 00 00 01 01 08 0a 0a 5f 95 d5 e4 14   .+=d......._....
0040   90 d7 00 00 00 24 66 6c 74 78 00 00 00 53 00 00   .....$fltx...S..
0050   07 d5 00 00 00 00 00 00 00 01 00 00 00 00 00 00   ................
0060   00 00 00 00 00 00 00 00 00 00                     ..........

My best guess: 0x7d8 indicates 'okay, I'm going to pause on this txid for a minute and do some other stuff, but I may come back to it'. I've witnessed it in packets where it was immediately proceeded by a command with a different txid:

0000   00 05 95 02 33 f9 00 05 95 02 34 0c 08 00 45 00   ....3.....4...E.
0010   00 58 76 a5 40 00 40 06 3e 03 c0 a8 02 54 c0 a8   .Xv.@.@.>....T..
0020   02 53 8d 3d 9a 7f bc b6 43 09 1a 86 23 28 80 18   .S.=....C...#(..
0030   02 20 a9 b9 00 00 01 01 08 0a 0a 5f 98 16 e4 14   . ........._....
0040   93 18 00 00 00 0c 66 6c 74 78 00 00 00 52 00 00   ......fltx...R..
0050   07 d8 00 00 00 10 66 6c 74 78 00 00 00 08 00 00   ......fltx......
0060   07 d9 00 00 00 01                                 ......

I suspect in the examples I posted above, the player meant to send the 0x7d8 command before it started requesting the first batch of transmissions for the other txid, but they just got sent out of order.

One interesting thing I noticed while looking for these, 0x7d8 was sent after a 0xa message response, which I've never seen before:

0000   00 05 95 02 34 0c 00 05 95 02 33 f9 08 00 45 00   ....4.....3...E.
0010   00 4d f1 b7 40 00 40 06 c2 fb c0 a8 02 53 c0 a8   .M..@[email protected]..
0020   02 54 9a 7f 8d 3d 1a 86 17 bf bc b6 42 e1 80 18   .T...=......B...
0030   13 7e 8d 96 00 00 01 01 08 0a e4 14 93 17 0a 5f   .~............._
0040   98 14 00 00 00 15 66 6c 74 78 00 00 00 52 00 00   ......fltx...R..
0050   00 0a 01 00 00 00 00 00 00 00 00                  ...........

This might be a "hey, are we still doing anything with this txid?" 0x7d8 tells it "yes, just not now".

@mhite
Copy link
Contributor

mhite commented May 7, 2023

Very interesting. In digging through the packet captures between the players, it seems like 0x7d9 is a pause request and the 0x7 is a pause response. The players seem to look for a bunch of different .db files and start downloading them all, but basically immediately puts each of those db transfers into a pause state.

I haven't made much sense of the x7d8, though :( It doesn't even contain a transferid so I'm a little suspicious there.

@mhite
Copy link
Contributor

mhite commented May 7, 2023

I've definitely hit a wall. Here's a few thoughts/theories:

  • The Prime definitely wants to find a bunch of database and other supporting files.
  • I created an ISO of a prepared USB stick and just mounted that into the directory I serve via my filetransfer server. I was hoping this gives the Denon exactly what it wants to find as I serve files.
  • My databases never actually download successfully to the Denon. I can see it jump from requesting chunk 0 all the way to chunk 12. It then determines the database is corrupt. (Display on Denon says that). This weird behavior does not happen in the packet captures between players that I've examined. (Unless I'm totally blind here.)
  • I think all the unknown fields in the stat messages screw up my ability to successfully serve files. I think there is something beyond the file size in those stat replies that is used in the Denon logic while it is requesting chunk downloads. (IE. My observation of jumping from chunk 0 to 12). Because I don't know the full structure of the stat response, I have basically dummy payloads copied from successful stats I've seen (except the file size part which is not copied).
  • My hunch is there is something about block sizes or an encoding of number of chunks in that stat response. Although I haven't been able to make heads or tails of it yet.
  • Side note, I think anywhere you see offset or filesize, they actually allocate 8 bytes for it. (Looking at @MarByteBeep's implementation, it seems to 4 bytes, but always has 4 bytes of "pad" 0's before it.)

If anyone has made new progress on decoding that stat message, let me know. I feel like it's what is probably holding me back.

@honusz
Copy link

honusz commented May 10, 2023

  • My databases never actually download successfully to the Denon. I can see it jump from requesting chunk 0 all the way to chunk 12. It then determines the database is corrupt. (Display on Denon says that). This weird behavior does not happen in the packet captures between players that I've examined. (Unless I'm totally blind here.)
  • I think all the unknown fields in the stat messages screw up my ability to successfully serve files. I think there is something beyond the file size in those stat replies that is used in the Denon logic while it is requesting chunk downloads. (IE. My observation of jumping from chunk 0 to 12). Because I don't know the full structure of the stat response, I have basically dummy payloads copied from successful stats I've seen (except the file size part which is not copied).
  • My hunch is there is something about block sizes or an encoding of number of chunks in that stat response. Although I haven't been able to make heads or tails of it yet.
    If anyone has made new progress on decoding that stat message, let me know. I feel like it's what is probably holding me back.

Ughh, this is driving me nuts too. The Fstat responses are just such a weird structure.

  • 49 (??) bytes after you exclude the first 16 bytes of length-fltx-txId-messageId
  • We know the last 4-bytes 8-Bytes! is filesize
  • So we have 45 bytes of 🤷🏻‍♂️

Of those 45 remaining bytes:

4-bytes: 01 00 00 00 for an existing file or 00 00 00 00 for (I assume) non-existing file
2-bytes: 77 55 for an existing file, 00 00 for non existing file.

So that leaves us with 39 bytes, which is an odd number, in every sense of the word.

Now, bear with me, I'm going to go a bit Carrie-from-Homeland here, lol.....

We see what looks like three repetitions of data, and hey! 39/3 = 13! So what we may be looking for is 13 bytes.

Note: I actually messed up here; I included the length-bytes in the byte-count, but with @mhite's observation about 8-byte filesize, we're in business!

Okay, so, now with it formatted a bit, it starts to make a bit more sense. Here's two valid(?) and one invalid(?) responses:

01 00 00 00 
77 55 
00 00 00 00 00 25 87 6e 04 2a b7 c4 00 
00 00 00 00 00 25 89 9c 04 0a 88 fa 00 
00 00 00 00 00 25 89 a1 04 d9 16 c0 00 
00 00 00 00 00 01 f0 00

01 00 00 00 
77 55 
00 00 00 00 00 25 88 0c 00 38 19 4a 00 
00 00 00 00 00 25 89 aa 04 bd 2f c8 00 
00 00 00 00 00 25 89 aa 04 bd 2a 50 00 
00 00 00 00 01 1d 10 00

00 00 00 00 
00 00 
80 00 00 00 00 00 00 00 ff ff ff ff 00 
80 00 00 00 00 00 00 00 ff ff ff ff 00 
80 00 00 00 00 00 00 00 ff ff ff ff 00 
00 00 00 00 00 00 00 00

Interestingly, when I look at my capture between the players, they return slightly differently:

01 00 00 00 
66 44 
80 00 00 00 00 00 00 00 ff ff ff ff 00 
00 00 00 00 00 25 88 c4 03 fe 6e 30 00 
00 00 00 00 00 25 88 c3 04 4a a2 00 00 
00 00 00 00 01 1f 62 75

Not sure what to make of that.

Anyway, not exactly a silver bullet, but we can say a few things about these 13-byte repetitions:

  • the first byte is 00 or 80
  • the next 4 bytes are 00
  • the 6th byte is always 25, if it's valid.
  • bytes 7-12 all seem to each have a limited number of possible values.
  • byte 13 always 00

Not sure if any of this is helpful.

Perhaps you could put the file on a usb, do 0x7d1 request on it, get the response, then try serving it to the player from your HD?

@mhite
Copy link
Contributor

mhite commented May 11, 2023

Great summary.

Of those 45 remaining bytes:

4-bytes: 01 00 00 00 for an existing file or 00 00 00 00 for (I assume) non-existing file

BTW --

The first byte is a boolean representing "Exists" truthiness.
The second byte is a boolean representing "IsDirectory" truthiness.

@mhite
Copy link
Contributor

mhite commented May 11, 2023

Perhaps you could put the file on a usb, do 0x7d1 request on it, get the response, then try serving it to the player from your HD?

I actually tried this. :( Sadly didn't do any good. So now I'm not sure of anything. :( :(

@honusz
Copy link

honusz commented May 11, 2023

I actually tried this. :( Sadly didn't do any good. So now I'm not sure of anything. :( :(

I'm pretty sure the 77 55 and 66 44 are the permissions.
image

For those other fields, perhaps we just need to collect more data, and see if a pattern emerges.

  • Are values unique?
  • If you make a copy of a file and rename it, does it have the same bytes? Similar?

@mhite
Copy link
Contributor

mhite commented May 11, 2023

I was also pondering that! But I'm not sure the first digit makes any sense.

For mounted filesystems like USB volumes, you would expect the mount to supply the default permissions since FAT32 has no inherent permissions concept. So it would make sense for USB volumes to have the same permissions across files and directories.

I like your approach. Perhaps a tool that recursively crawls the directory path and records the stat bytes for each file and directory.

@mhite
Copy link
Contributor

mhite commented May 11, 2023

A thought on the three similar chunks -- perhaps access, modification, and change time?

That being said, I tried a million different ways of decoding them or portions of them as timestamps with nothing sane found.

@mhite
Copy link
Contributor

mhite commented May 11, 2023

Other things that might show up:

  • User id
  • Group id
  • Device id
  • Blocks
  • IO block size

@mhite
Copy link
Contributor

mhite commented May 12, 2023

If you make a copy of a file and rename it, does it have the same bytes? Similar?

stat from original file:

    00000000  00 00 77 55 00 00 00 00  00 25 87 09 03 5c 68 42  |..wU.....%...\hB|
    00000010  00 00 00 00 00 00 25 87  09 03 25 ff f0 00 00 00  |......%...%.....|
    00000020  00 00 00 25 89 ac 02 8c  6f 70 00                 |...%....op.|

copy of file:

    00000000  00 00 77 55 00 00 00 00  00 25 89 ac 02 8c 58 00  |..wU.....%....X.|
    00000010  00 00 00 00 00 00 25 89  ac 02 8c 59 c2 00 00 00  |......%....Y....|
    00000020  00 00 00 25 89 ac 02 8c  5f d0 00                 |...%...._..|

another copy of the original, made a few seconds later:

    00000000  00 00 77 55 00 00 00 00  00 25 89 ac 02 8c 58 00  |..wU.....%....X.|
    00000010  00 00 00 00 00 00 25 89  ac 02 8c 59 c2 00 00 00  |......%....Y....|
    00000020  00 00 00 25 89 ac 02 8c  5f d0 00                 |...%...._..|

copy 1 and copy 2 share those last bytes:

00 00 00 25 89 ac 02 8c  5f d0 00 

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

No branches or pull requests

5 participants