Я уверен, что многие из читателей Хабра знают, или хотя бы слышали, об аудио-аппаратуре компании Onkyo. Современные сетевые плееры и A/V ресиверы имеют на борту Линукс, а также возможность проводного/беспроводного подключения к сети. Компания Onkyo предоставляет своё фирменное мобильное приложение для удалённого управления подобным устройством — Onkyo Controller. Информации, как это приложение работает, практически нет — есть крохи на форумах, а также несколько проектов на github.
Но можно отыскать в сети описание протокола Integra Serial Communication Protocol over Ethernet (eISCP), который и лежит в основе этого приложения. Протокол интересный. На Хабре ни одной статьи по этому протоколу найти не удалось. С одной стороны, ничего трагичного в этом нет, так как эта проприетарщина нигде, кроме Onkyo, вроде бы и не используется. С другой стороны есть шанс, что найдутся энтузиасты, которые захотят самостоятельно порулить своим плеером или ресивером Onkyo. Также статья может быть интересна тем, кто чисто из теоретического любопытства коллекционирует знания по различным сетевым протоколам. Если заинтересовал, прошу под кат.
Официальной информации по теме статьи мало. Поэтому я буду опираться не только на найденную документацию, так как она описывает исключительно команды протокола, но ничего не говорит об особенностях их использования. Много информации удалось получить как из анализа сетевого трафика с использованием tcpdump/wireshark, так и исследования прошивки устройства. Именно с этого я и начну.
Конкретная модель моего устройства не важна. Скажу только, что это сетевой плеер, похожий на тот, что на картинке для привлечения внимания. Он может проигрывать музыку не только с внешних USB-носителей, но и с музыкального сервера (DLNA), а также поддерживает интернет-радио и потоковые сервисы типа Spotify, Deezer и ещё что-то. Естественно, протокол должен всё это разнообразие поддерживать.
> nmap -sS -p0-65535 -T5 192.168.1.80
PORT STATE SERVICE
80/tcp open http
4545/tcp open worldscores
5000/tcp open upnp
8008/tcp open http
8009/tcp open ajp13
8080/tcp open http-proxy
8888/tcp open sun-answerbook
10001/tcp open scp-config
60128/tcp open unknown
{
"data": {
"fireCast": false,
"status": {
"duration": 224893,
"playBytes": 0
},
"error": "",
"matchingMediaRoles": [],
"controls": {
"previous": true,
"next_": true,
"seekBytes": true,
"seekTime": true,
"pause": true,
"seekTrack": true
},
"mediaRoles": {
"title": "",
"asciiTitle": ""
},
"playId": {
"systemMemberId": "Onkyo NS-6130",
"timestamp": 447085
},
"state": "playing",
"trackRoles": {
"mediaData": {
"metaData": {
"artist": "Ottawan",
"album": "Greatest Hits",
"serviceID": "Storage_usb2"
}
},
"title": "Shalala-Song",
"flags": {
"file": true
},
"path": "storage_file_usb2:sda-94DB-FB8F/flac/Disco/Ottawan/Greatest Hits (2007)/05-Shalala-Song.flac",
"optPlayingConentInfo": {
"playingTrackTotal": 17,
"playingTrackNo": 4
},
"icon": "file:///tmp/temp_data_albumArt_3c70a403584dc761cabc88ac0dfbb95c",
"type": "audio"
}
},
"playTime": {
"i64_": 139021,
"type": "i64_"
},
"senderVolume": {},
"senderMute": {},
"sender": "Onkyo-NS-6130-E1EE7F"
}
> adb root && adb shell
root@fiber-bs1078:/>
root@fiber-bs1078:/> cd /sdcard/work
root@fiber-bs1078:/sdcard/work>
root@fiber-bs1078:/sdcard/work> tcpdump -vX -i any -w onkyo.dump host 192.168.1.80
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
root@fiber-bs1078:/sdcard/work> exit
> adb pull /sdcard/work/onkyo.dump .
[100%] /sdcard/work/onkyo.dump
> wireshark onkyo.dump &
SERVICE=TUNEIN_RADIO; UI=LIST; LAYER=SERVICE_TOP; CURSOR=0; ITEMS=9; LAYERS=1; START=NOT_FIRST; LEFT_ICON=NONE; RIGHT_ICON=TUNEIN_RADIO; STATUS=NONE; title=TuneIn Radio
<?xml version="1.0" encoding="utf-8"?>
<response status="ok">
<items offset="0" totalitems="9">
<item icontype="F" iconid="29" title="My Presets" selectable="1" />
<item icontype="F" iconid="29" title="Local Radio" selectable="1" />
<item icontype="F" iconid="29" title="Music" selectable="1" />
<item icontype="F" iconid="29" title="Talk" selectable="1" />
<item icontype="F" iconid="29" title="Sports" selectable="1" />
<item icontype="F" iconid="29" title="By Location" selectable="1" />
<item icontype="F" iconid="29" title="By Language" selectable="1" />
<item icontype="F" iconid="29" title="Podcasts" selectable="1" />
<item icontype="F" iconid="29" title="Login" selectable="1" />
</items>
</response>
<?xml version="1.0" encoding="utf-8"?>
<popup title="Try Deezer Premium+" align="center" type="custom" time="0" uri="resource:///popup">
<label title="" align="center" total="1" uri="resource:///popup/label:0">
<line text="Listening is limited to 30-second clips. Subscribe to enjoy unlimited music!"
align="left" uri="resource:///popup/label/line:0" order="0" />
</label>
<buttongroup title="" align="center" total="1" uri="resource:///popup/buttongroup:0">
<button text="OK"
align="center" uri="/button:0" selected="false" index="0" www="" order="1" />
</buttongroup>
</popup>
<?xml version="1.0" encoding="utf-8"?>
<response status="ok">
<device id="NS-6130">
<brand>ONKYO</brand>
<category>NAP-O</category>
<year>2016</year>
<model>NS-6130</model>
<destination>xx</destination>
<macaddress>0009B0E1EE7F</macaddress>
<modeliconurl>http://192.168.1.80/icon/OAVR_120.jpg</modeliconurl>
<friendlyname></friendlyname>
<firmwareversion>2110-0000-0000-0010-0000</firmwareversion>
<ecosystemversion>200</ecosystemversion>
<netservicelist count="9">
<netservice id="0e" value="1" name="TuneIn Radio" account="Username" password="Password"
zone="01" enable="01" />
<netservice id="0a" value="1" name="Spotify" zone="01" enable="01" />
<netservice id="12" value="1" name="Deezer" account="Email address" password="Password"
zone="01" enable="01" />
<netservice id="18" value="1" name="AirPlay" zone="01" enable="01" />
<netservice id="1b" value="1" name="TIDAL" account="Username" password="Password"
zone="01" enable="01" />
<netservice id="00" value="1" name="Music Server" zone="01" enable="01" addqueue="1"
sort="1" />
<netservice id="43" value="1" name="FlareConnect" zone="07" enable="0e" />
<netservice id="40" value="1" name="Chromecast built-in" zone="01" enable="01" />
<netservice id="1d" value="1" name="Play Queue" zone="01" enable="01" />
</netservicelist>
<zonelist count="4">
<zone id="1" value="1" name="Main" volmax="0" volstep="0" src="1" dst="1"
lrselect="0" />
<zone id="2" value="0" name="Zone2" volmax="0" volstep="0" src="0" dst="0"
lrselect="0" />
<zone id="3" value="0" name="Zone3" volmax="0" volstep="0" src="0" dst="0"
lrselect="0" />
<zone id="4" value="0" name="Zone4" volmax="0" volstep="0" src="0" dst="0"
lrselect="0" />
</zonelist>
<selectorlist count="3">
<selector id="2b" value="1" name="NET" zone="01" iconid="2b" />
<selector id="29" value="1" name="USB(F)" zone="01" iconid="29" addqueue="1" />
<selector id="2a" value="1" name="USB(R)" zone="01" iconid="2a" addqueue="1" />
</selectorlist>
<presetlist count="40">
<preset id="01" band="0" freq="0" name="" />
<preset id="02" band="0" freq="0" name="" />
<preset id="03" band="0" freq="0" name="" />
<preset id="04" band="0" freq="0" name="" />
<preset id="05" band="0" freq="0" name="" />
<preset id="06" band="0" freq="0" name="" />
<preset id="07" band="0" freq="0" name="" />
<preset id="08" band="0" freq="0" name="" />
<preset id="09" band="0" freq="0" name="" />
<preset id="0a" band="0" freq="0" name="" />
<preset id="0b" band="0" freq="0" name="" />
<preset id="0c" band="0" freq="0" name="" />
<preset id="0d" band="0" freq="0" name="" />
<preset id="0e" band="0" freq="0" name="" />
<preset id="0f" band="0" freq="0" name="" />
<preset id="10" band="0" freq="0" name="" />
<preset id="11" band="0" freq="0" name="" />
<preset id="12" band="0" freq="0" name="" />
<preset id="13" band="0" freq="0" name="" />
<preset id="14" band="0" freq="0" name="" />
<preset id="15" band="0" freq="0" name="" />
<preset id="16" band="0" freq="0" name="" />
<preset id="17" band="0" freq="0" name="" />
<preset id="18" band="0" freq="0" name="" />
<preset id="19" band="0" freq="0" name="" />
<preset id="1a" band="0" freq="0" name="" />
<preset id="1b" band="0" freq="0" name="" />
<preset id="1c" band="0" freq="0" name="" />
<preset id="1d" band="0" freq="0" name="" />
<preset id="1e" band="0" freq="0" name="" />
<preset id="1f" band="0" freq="0" name="" />
<preset id="20" band="0" freq="0" name="" />
<preset id="21" band="0" freq="0" name="" />
<preset id="22" band="0" freq="0" name="" />
<preset id="23" band="0" freq="0" name="" />
<preset id="24" band="0" freq="0" name="" />
<preset id="25" band="0" freq="0" name="" />
<preset id="26" band="0" freq="0" name="" />
<preset id="27" band="0" freq="0" name="" />
<preset id="28" band="0" freq="0" name="" />
</presetlist>
<controllist count="61">
<control id="Bass" value="0" zone="1" min="-10" max="10" step="2" />
<control id="Treble" value="0" zone="1" min="-10" max="10" step="2" />
<control id="Center Level" value="0" zone="1" min="-12" max="12" step="1" />
<control id="Subwoofer Level" value="0" zone="1" min="-15" max="12" step="1" />
<control id="Subwoofer1 Level" value="0" zone="1" min="-15" max="12" step="1" />
<control id="Subwoofer2 Level" value="0" zone="1" min="-15" max="12" step="1" />
<control id="Phase Matching Bass" value="0" />
<control id="LMD Movie/TV" value="0" code="MOVIE" position="1" />
<control id="LMD Music" value="0" code="MUSIC" position="2" />
<control id="LMD Game" value="0" code="GAME" position="3" />
<control id="LMD THX" value="0" code="04" position="4" />
<control id="LMD Stereo" value="0" code="00" position="4" />
<control id="LMD Direct" value="0" code="01" position="1" />
<control id="LMD Pure Audio" value="0" code="11" position="2" />
<control id="LMD Pure Direct" value="0" code="11" position="1" />
<control id="LMD Auto/Direct" value="0" code="AUTO" position="2" />
<control id="LMD Stereo G" value="0" code="STEREO" position="3" />
<control id="LMD Surround" value="0" code="SURR" position="4" />
<control id="TUNER Control" value="0" />
<control id="TUNER Freq Control" value="0" />
<control id="Info" value="2" />
<control id="Cursor" value="1" />
<control id="Home" value="0" code="HOME" position="2" />
<control id="Setup" value="1" code="MENU" position="2" />
<control id="Quick" value="0" code="QUICK" position="1" />
<control id="Menu" value="0" code="MENU" position="1" />
<control id="AMP Control(RI)" value="1" />
<control id="CD Control(RI)" value="1" />
<control id="CD Control" value="0" />
<control id="BD Control(CEC)" value="0" />
<control id="TV Control(CEC)" value="0" />
<control id="NoPowerButton" value="0" />
<control id="DownSample" value="0" />
<control id="Dimmer" value="1" />
<control id="time_hhmmss" value="1" />
<control id="Zone2 Control(CEC)" value="0" />
<control id="Sub Control(CEC)" value="0" />
<control id="NoNetworkStandby" value="0" />
<control id="NJAREQ" value="1" />
<control id="Music Optimizer" value="0" />
<control id="NoVideoInfo" value="1" />
<control id="NoAudioInfo" value="1" />
<control id="AV Adjust" value="0" />
<control id="Audio Scalar" value="0" />
<control id="Hi-Bit" value="0" />
<control id="Upsampling" value="0" />
<control id="Digital Filter" value="1" />
<control id="DolbyAtmos" value="0" />
<control id="DTS:X" value="0" />
<control id="MCACC" value="0" />
<control id="Dialog Enhance" value="0" />
<control id="PQLS" value="0" />
<control id="CD Control(NewRemote)" value="0" />
<control id="NoVolume" value="1" />
<control id="Auto Sound Retriever" value="0" />
<control id="Lock Range Adjust" value="0" />
<control id="P.BASS" value="0" />
<control id="Tone Direct" value="0" />
<control id="DetailedFileInfo" value="1" />
<control id="NoDABPresetFunc" value="0" />
<control id="S.BASS" value="0" />
</controllist>
<functionlist count="10">
<function id="UsbUpdate" value="0" />
<function id="NetUpdate" value="1" />
<function id="WebSetup" value="1" />
<function id="WifiSetup" value="1" />
<function id="Nettune" value="0" />
<function id="Initialize" value="0" />
<function id="Battery" value="0" />
<function id="AutoStandbySetting" value="0" />
<function id="e-onkyo" value="0" />
<function id="UsbDabDongle" value="0" />
</functionlist>
<tuners count="0"></tuners>
</device>
</response>
private final static int MIN_MSG_LENGTH = 22;
private final static String MSG_START = "ISCP";
private final static Character START_CHAR = '!';
private final static int LF = 0x0A;
...
byte[] getBytes()
{
if (headerSize + dataSize < MIN_MSG_LENGTH)
{
return null;
}
final byte[] bytes = new byte[headerSize + dataSize];
Arrays.fill(bytes, (byte) 0);
// Message header
for (int i = 0; i < MSG_START.length(); i++)
{
bytes[i] = (byte) MSG_START.charAt(i);
}
// Header size
byte[] size = ByteBuffer.allocate(4).putInt(headerSize).array();
System.arraycopy(size, 0, bytes, 4, size.length);
// Data size
size = ByteBuffer.allocate(4).putInt(dataSize).array();
System.arraycopy(size, 0, bytes, 8, size.length);
// Version
bytes[12] = (byte) version;
// CMD
bytes[16] = (byte) START_CHAR.charValue();
bytes[17] = (byte) '1';
for (int i = 0; i < code.length(); i++)
{
bytes[i + 18] = (byte) code.charAt(i);
}
// Parameters
for (int i = 0; i < parameters.length(); i++)
{
bytes[i + 21] = (byte) parameters.charAt(i);
}
// End char
bytes[21 + parameters.length()] = (byte) LF;
return bytes;
}
10-27 16:12:20.272: NLU[00080011; 8/17]
10-27 16:12:27.338: NTI[09-Roses Are Red.flac]
10-27 16:12:27.342: NAL[]
10-27 16:12:27.342: NAT[]
10-27 16:12:27.342: NDN[]
10-27 16:12:27.343: NJA/1937[2-...; TYPE=URL; PACKET=NOT_USED; URL=http://192.168.1.80/album_art.cgi; RAW(null)]
10-27 16:12:27.649: NMS[xxxxxS1f1; TRACK_MENU=DISABLE; POS_FEED=DISABLE; NEG_FEED=DISABLE; TIME_SEEK=ENABLE; TIME_DISPLAY=ELAPSED_TOTAL; ICON=USB_REAR]
10-27 16:12:27.649: NTR[0009; 0011]
10-27 16:12:27.649: NFI[/44.1kHz/16bit; FORMAT=; FREQUENCY=44.1kHz; BITRATE=16bit]
10-27 16:12:27.649: NLT[F1020000000B060002FF00Aquarium (1997); SERVICE=USB_REAR; UI=LIST; LAYER=UNDER_2ND_LAYER; CURSOR=0; ITEMS=11; LAYERS=6; START=NOT_FIRST; LEFT_ICON=USB; RIGHT_ICON=NONE; STATUS=NONE; title=Aquarium (1997)]
10-27 16:12:27.724: NLS[C0P; INF_TYPE=CURSOR; LINE_INFO=0; PROPERTY=NO; UPD_TYPE=PAGE; LIST_DATA=null]
10-27 16:12:27.727: NLS[U0-Happy Boys & Girls; INF_TYPE=UNICODE; LINE_INFO=0; PROPERTY=NO; UPD_TYPE=NO; LIST_DATA=Happy Boys & Girls]
10-27 16:12:27.734: NLS[U1-My Oh My; INF_TYPE=UNICODE; LINE_INFO=1; PROPERTY=NO; UPD_TYPE=NO; LIST_DATA=My Oh My]
10-27 16:12:27.737: NLS[U2-Barbie Girl; INF_TYPE=UNICODE; LINE_INFO=2; PROPERTY=NO; UPD_TYPE=NO; LIST_DATA=Barbie Girl]
10-27 16:12:27.740: NLS[U3-Good Morning Sunshine; INF_TYPE=UNICODE; LINE_INFO=3; PROPERTY=NO; UPD_TYPE=NO; LIST_DATA=Good Morning Sunshine]
10-27 16:12:27.760: NLA[X0002S000...; RESP=X; SEQ_NR=2; STATUS=S; UI=LIST; XML=<?xml version="1.0" encoding="utf-8"?><response status="ok"><items offset="0" totalitems="11" ><item icontype="M" iconid="2d" title="Happy Boys & Girls" selectable="1" /><item icontype="M" iconid="2d" title="My Oh My" selectable="1" /><item icontype="M" iconid="2d" title="Barbie Girl" selectable="1" /><item icontype="M" iconid="2d" title="Good Morning Sunshine" selectable="1" /><item icontype="M" iconid="2d" title="Doctor Jones" selectable="1" /><item icontype="M" iconid="2d" title="Heat Of The Night" selectable="1" /><item icontype="M" iconid="2d" title="Be A Man" selectable="1" /><item icontype="M" iconid="2d" title="Lollipop (Candyman)" selectable="1" /><item icontype="0" iconid="36" title="Roses Are Red" selectable="1" /><item icontype="M" iconid="2d" title="Turn Back Time" selectable="1" /><item icontype="M" iconid="2d" title="Calling You" selectable="1" /></items></response>]
10-27 16:12:29.697: NTI[Roses Are Red]
10-27 16:12:29.718: NJA/1952[2-...; TYPE=URL; PACKET=NOT_USED; URL=http://192.168.1.80/album_art.cgi; RAW(null)]
10-27 16:12:30.248: NAL[Aquarium]
10-27 16:12:30.248: NAT[Aqua]
10-27 16:12:30.248: NDN[]
10-27 16:12:30.248: NMS[xxxxxS1f1; TRACK_MENU=DISABLE; POS_FEED=DISABLE; NEG_FEED=DISABLE; TIME_SEEK=ENABLE; TIME_DISPLAY=ELAPSED_TOTAL; ICON=USB_REAR]
10-27 16:12:30.248: NTR[0009; 0011]
10-27 16:12:30.248: NFI[FLAC/44.1kHz/16bit; FORMAT=FLAC; FREQUENCY=44.1kHz; BITRATE=16bit]
10-27 16:12:30.248: NLT[F1020000000B060002FF00Aquarium (1997); SERVICE=USB_REAR; UI=LIST; LAYER=UNDER_2ND_LAYER; CURSOR=0; ITEMS=11; LAYERS=6; START=NOT_FIRST; LEFT_ICON=USB; RIGHT_ICON=NONE; STATUS=NONE; title=Aquarium (1997)]
10-27 16:12:30.248: NMS[xxxxxS1f1; TRACK_MENU=DISABLE; POS_FEED=DISABLE; NEG_FEED=DISABLE; TIME_SEEK=ENABLE; TIME_DISPLAY=ELAPSED_TOTAL; ICON=USB_REAR]
10-27 16:12:30.248: NFI[FLAC/44.1kHz/16bit; FORMAT=FLAC; FREQUENCY=44.1kHz; BITRATE=16bit]
10-27 16:12:30.248: NLT[F1020000000B060002FF00Aquarium (1997); SERVICE=USB_REAR; UI=LIST; LAYER=UNDER_2ND_LAYER; CURSOR=0; ITEMS=11; LAYERS=6; START=NOT_FIRST; LEFT_ICON=USB; RIGHT_ICON=NONE; STATUS=NONE; title=Aquarium (1997)]
10-27 16:12:34.815: NMS[xxxxxS1f1; TRACK_MENU=DISABLE; POS_FEED=DISABLE; NEG_FEED=DISABLE; TIME_SEEK=ENABLE; TIME_DISPLAY=ELAPSED_TOTAL; ICON=USB_REAR]
10-27 16:12:34.819: NFI[FLAC/44.1kHz/16bit; FORMAT=FLAC; FREQUENCY=44.1kHz; BITRATE=16bit]
10-27 16:12:34.860: NLT[F1020000000B060002FF00Aquarium (1997); SERVICE=USB_REAR; UI=LIST; LAYER=UNDER_2ND_LAYER; CURSOR=0; ITEMS=11; LAYERS=6; START=NOT_FIRST; LEFT_ICON=USB; RIGHT_ICON=NONE; STATUS=NONE; title=Aquarium (1997)]
К сожалению, не доступен сервер mySQL