Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
WebUSBでレイヤーが低まるWeb開発
Search
Fadis
September 23, 2017
Programming
27
17k
WebUSBでレイヤーが低まるWeb開発
Chrome 61から使えるようになったWebUSB APIを使ってUSBデバイスと会話する方法を解説します。
これは2017年9月23日に行われた 第3回 カーネル/VM探検隊@北陸の発表資料です。
Fadis
September 23, 2017
Tweet
Share
More Decks by Fadis
See All by Fadis
コンピュータグラフィクスの空
fadis
7
1.7k
NNEFを読めるようになろう
fadis
0
750
低レイヤーから始める GUI
fadis
18
11k
いまどきのVulkan
fadis
14
7k
L2 WireGuard
fadis
2
3.8k
Wasserstein逆FM音源
fadis
3
3.4k
HSEとは何か
fadis
15
3.1k
逆FM音源
fadis
24
16k
WebUSBとは何か、何を引き起こしたか、今どうなっているか
fadis
12
6.9k
Other Decks in Programming
See All in Programming
So You Think You Know Git - Part 2
schacon
PRO
0
1.3k
私がエッジを使う理由
chimame
9
3.6k
before_rails_girls_after_rails_girls
maimux2x
0
300
Faster, greener, and happier- why Quarkus should be your next tech stack
hollycummins
0
130
TerraformをやめてCDKでReStartしたあと、 CDKをやめてCDK for TerraformでReStartした話
tmiura0203
0
770
UnityプログラミングバイブルR6号宣伝&Unity Logging小話
adarapata
0
110
メール認証とRuby
uvb_76
0
110
「Hono遍歴」と「HonoXでブログ作成」
yasu551
0
170
PHPerKaigi 2024〜10年以上動いているレガシーなバッチシステムを Kubernetes(Amazon EKS) に移行する取り組み〜
tshinowpub
1
170
CSC308B Lecture 12
javiergs
PRO
0
110
25 Years of the JCP Program
ivargrimstad
0
1k
PHPカンファレンス関西2024でLTとスタッフした
ohmori_yusuke
2
120
Featured
See All Featured
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
56
13k
Code Review Best Practice
trishagee
54
15k
Fantastic passwords and where to find them - at NoRuKo
philnash
35
2.4k
In The Pink: A Labor of Love
frogandcode
137
21k
How GitHub Uses GitHub to Build GitHub
holman
467
290k
Embracing the Ebb and Flow
colly
78
4.1k
How STYLIGHT went responsive
nonsquared
92
4.7k
Statistics for Hackers
jakevdp
789
220k
ParisWeb 2013: Learning to Love: Crash Course in Emotional UX Design
dotmariusz
101
6.6k
Optimizing for Happiness
mojombo
369
69k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
4
1.4k
Robots, Beer and Maslow
schacon
PRO
154
7.9k
Transcript
WebUSBͰ ·ΔWeb։ൃ ϨΠϠʔ͕ NAOMASA MATSUBAYASHI
WebUSB API http://wicg.github.io/webusb
ϗετʹଓ͞ΕͨUSBσόΠεͱϒϥβ͕ OSͷσόΠευϥΠόΛհͣ͞ʹ ձ͢ΔͨΊͷJavaScriptͷAPI Χʔωϧ ϒϥβ 64#σόΠε WebUSB API libusbΛWeb͔Βୟ͚ΔΑ͏ʹͳͬͨͱࢥ͑ ͍͍͍ͩͨ͋ͬͯΔ
σόΠεΛऔಘ͢Δ return navigator.usb.requestDevice( { 'filters': [ { 'vendorId': 0x1234, 'productId':
0x5678 } ] } ).then(device_ => { device = device_; return connect(); }).catch(error => { console.log('ଓΤϥʔ: ' + error); }); navigator.usb.requestDevieͰ ࢦఆͨ͠ϕϯμIDͱϓϩμΫτIDΛ࣋ͭ USBσόΠεͷଓΛཁٻ͢Δ
σόΠεΛऔಘ͢Δ ͜Μͳϓϩϯϓτ͕ग़ͯ͘Δ ϢʔβWebϖʔδʹ৮Βͤͯྑ͍σόΠεΛબͿ
௨৴Ͱ͖Δঢ়ଶʹ͢Δ return device.open().then(() => { if(device.configuration === null) { return
device.selectConfiguration(1); } }).then(() => { return device.claimInterface(1); }).then(() => { return device.claimInterface(0); }); configurationΛબͿ USBσόΠεෳͷػೳΛఏڙ͍ͯ͠Δ͜ͱ͕͋Δ ͲͷػೳΛ͏͔ΛબͿͷ͕configurationͷબ ඞཁͳΠϯλʔϑΣʔεΛ༗͢Δ
σόΠεʹσʔλΛૹΔ/͏ device.transferIn(1, 1024).then(result => { something_on_recieved( result.data ); } σόΠε͔Βड৴
σόΠεʹૹ৴ ΤϯυϙΠϯτID ड৴͢ΔόΠτ PromiseͰड৴݁ՌͱDataView͕ฦͬͯདྷΔ device.transferOut(2, data).then(result => { something_on_sent(); } ΤϯυϙΠϯτID ૹ৴͢ΔArrayBuffer PromiseͰૹ৴݁Ռ͕ฦͬͯདྷΔ
σόΠεʹσʔλΛૹΔ/͏ device.controlTransferOut({ 'requestType': 'class', 'recipient': 'interface', 'request': 0x22, 'value': 0x01,
'index': 0x00 }).then(result => { … } ίϯτϩʔϧసૹ device.controlTransferIn({ 'requestType': 'standard', 'recipient': 'device', 'request': 0x06, 'value': 0x0100, 'index': 0x0000 }, 0x12).then(result => { … } σόΠεͷใͷऔಘ σόΠεͷॳظԽΛߦ͏
CDC ACM γϦΞϧ௨৴ Webϒϥβͷதͱ֎Ͱձͯ͠ΈΑ͏
$ modprobe libcomposite $ modprobe dummy_hcd $ cd /sys/kernel/config/usb_gadget $
mkdir g1 $ cd g1 $ mkdir functions/acm.g1 $ mkdir configs/c.1 $ ln -s functions/acm.g1 configs/c.1 $ echo 0x1234 >idVendor $ echo 0x5678 >idProduct $ echo dummy_udc.0 >UDC $ sleep 1 $ modprobe -r cdc_acm $ agetty 115200 ttyGS0 LinuxͷUSB GadgetͰ CDC ACMͷγϦΞϧ௨৴σόΠεΛ࡞Γ σόΠεଆͰgetty͢Δ ϗετଆυϥΠόΞϯϩʔυ͓ͯ͘͠
function flush() { writing = true; let buffer_ = buffer;
buffer = ''; device.transferOut(2, textEncoder.encode(buffer_)).then( result => { writing = false; if( buffer.length != 0 ) { flush(); } }).catch(error => { console.log(‘ૹ৴Τϥʔ: ' + error); writing = false; if( buffer.length != 0 ) { flush(); } }); } t.open( document.getElementById('terminal') ); t.on('key', function (key, ev) { if(device !== undefined) { if( !writing ) { buffer += key; flush(); } else { buffer += key; } } }); xterm.jsʹΩʔೖྗ͕͋ͬͨΒ ΤϯυϙΠϯτʹೖྗ༰Λྲྀ͢
let readLoop = () => { if( device ) {
device.transferIn(1, 1024).then(result => { let textDecoder = new TextDecoder(); t.write(textDecoder.decode(result.data)); readLoop(); }, error => { console.log( error ); readLoop(); }); } }; ΤϯυϙΠϯτ1Ͱ σόΠε͔ΒͷσʔλΛͪड͚ Կ͔ड͚औͬͨΒxterm.jsʹྲྀ͢
USBΛ௨ͬͯ֎ͷੈքʹඈͼग़͢Α! http://fadis.github.io/webusb/minimal/
USBϚεετϨʔδ USB֎͚ϋʔυσΟεΫ USBϑϥογϡϝϞϦͱձͯ͠ΈΑ͏
͜ͷUSBϑϥογϡϝϞϦΛ ϒϥβ͔ΒಡΉ $ modprobe -r uas $ modprobe -r mass_storage
·ͣΧʔωϧ͕अຐΛ͠ͳ͍Α͏ʹ͓ͯ͘͠
Command Block Wrapper SCSIίϚϯυ Command State Wrapper ίϚϯυͷ࣮ߦ݁Ռ ίϚϯυʹਵ͢Δ σʔλΛૹड৴
Bulk Only Transport ࡉ͔͍ετϨʔδͷૢ࡞ํ๏SCSIͦͷ··
Command Block Wrapper let cbw = ( tag, len, dir,
command ) => { let data = new Uint8Array( 15 + 16 ); data.set([ 0x55, 0x53, 0x42, 0x43, // USBC tag & 0xFF, ( tag >> 8 ) & 0xFF, ( tag >> 16 ) & 0xFF, ( tag >> 24 ) & 0xFF, // tag len & 0xFF, ( len >> 8 ) & 0xFF, ( len >> 16 ) & 0xFF, ( len >> 24 ) & 0xFF, // len dir << 7, // flags 0, // LUN command.byteLength // command length ], 0); data.set( command, 15 ); return device.transferOut( 2, data ); } 4$4*ίϚϯυʹ $#8ͷ ϔομΛ͚ͭͯ σόΠεʹ͛Δ
Command State Wrapper let csw = () => { return
device.transferIn( 1, 13 ).then( result => { let state = result.data.getUint8( 12 ); return state == 0; } ); } ίϚϯυͷ࣮ߦ݁ՌΛσόΠε͔Βड͚औΔ
ετϨʔδΛಡΉͷʹඞཁͳSCSIίϚϯυ INQUIRY TEST UNIT READY State? READ CAPACITY(10) READ(10) OK
* -6/ʹσόΠε͕ ͋Δ͜ͱΛ֬ೝ σόΠε͕ ར༻ՄೳʹͳΔͷΛͭ σόΠεͷ༰ྔͱ ηΫλαΠζΛऔಘ σόΠε͔Β σʔλΛಡΈग़͢
let inquiry = () => { let command = new
Uint8Array([ // INQUIRY LUN reserved reserved size reserved 0x12, 0, 0, 0, 36, 0 ]); return cbw( 1, 36, 1, command ).then( result => { return device.transferIn( 1, 36 ); }).then( result => { return result.data; }).then( data => { return csw().then( stat => { return { 'status': stat, 'data': data } }); }); } ඞཁͳSCSIίϚϯυΛ࣮͍ͯ͘͠
ϚελʔϒʔτϨίʔυ ύʔςΟγϣϯ ςʔϒϧͷ ͭΊͷΤϯτϦ ϒʔτγάχνϟ -#"͔Β ϒϩοΫΛऔಘ 55 aa
BIOSύϥϝʔλϒϩοΫ FAT32ͷϔομ 46 41 54 33 32 20 20 20
F A T 3 2 _ _ _
FAT32 let load_fat32 = partition => { return read( partition.at,
1 ).then( result => { let cluster_size = result.data.getUint8( 13 ); let reserved_sector_size = result.data.getUint16( 14, true ); let num_fats = result.data.getUint8( 16 ); let rde_size = result.data.getUint16( 17, true ); let fat_size = result.data.getUint32( 36, true ); let rde = result.data.getUint32( 44, true ); let fat_lba = partition.at + reserved_sector_size; return read( fat_lba, fat_size ).then( result => { let len = result.data.byteLength / 4; let fat = new Uint32Array( len ); for( let i = 0; i != len; i++ ) { fat[ i ] = result.data.getUint32( i * 4, true ); } let clusters_lba = fat_lba + num_fats * fat_size; let fsinfo = { 'cluster_size': cluster_size, 'fat': fat, 'clusters_lba': clusters_lba, 'rootdir_entry': rde BIOSύϥϝʔλϒϩοΫ͔Β ϑΝΠϧγεςϜΛಡΉͷʹඞཁͳΛऔಘ ΫϥελαΠζ FATͷͱαΠζͱ։࢝ηΫλ ϧʔτσΟϨΫτϦ͕ॻ͔Ε͍ͯΔҐஔ
FAT32 result.data.getUint16( 14, true ); let num_fats = result.data.getUint8( 16
); let rde_size = result.data.getUint16( 17, true ); let fat_size = result.data.getUint32( 36, true ); let rde = result.data.getUint32( 44, true ); let fat_lba = partition.at + reserved_sector_size; return read( fat_lba, fat_size ).then( result => { let len = result.data.byteLength / 4; let fat = new Uint32Array( len ); for( let i = 0; i != len; i++ ) { fat[ i ] = result.data.getUint32( i * 4, true ); } let clusters_lba = fat_lba + num_fats * fat_size; let fsinfo = { 'cluster_size': cluster_size, 'fat': fat, 'clusters_lba': clusters_lba, 'rootdir_entry': rde }; return load_fat32file( fsinfo, fsinfo.rootdir_entry, 0 ).then( raw_root_dir => { let root_dir = parse_fat32directory( raw_root_dir ); FATʹ ࠓಡΜͰ͍ΔΫϥελͷ࣍ʹಡΉ͖Ϋϥελ͕ Ͳ͔͕͜ه͞Ε͍ͯΔ FATΛಡΉ
FAT32 let fat = new Uint32Array( len ); for( let
i = 0; i != len; i++ ) { fat[ i ] = result.data.getUint32( i * 4, true ); } let clusters_lba = fat_lba + num_fats * fat_size; let fsinfo = { 'cluster_size': cluster_size, 'fat': fat, 'clusters_lba': clusters_lba, 'rootdir_entry': rde }; return load_fat32file( fsinfo, fsinfo.rootdir_entry, 0 ).then( raw_root_dir => { let root_dir = parse_fat32directory( raw_root_dir ); fsinfo[ 'rootdir' ] = root_dir; return fsinfo; }); }); }); }; ϧʔτσΟϨΫτϦΛಡΉ
let get_fat32clusters_reversed = ( fsinfo, head ) => { let
next = fsinfo.fat[ head ] & 0x0FFFFFFF; if( next >= 0x00000002 && next <= 0x0ffffff6 ) { let tail = get_fat32clusters_reversed( fsinfo, next ); tail.push( head ); return tail; } else return [ head ]; }; ΫϥελνΣΠϯ FAT͔ΒಡΉඞཁ͕͋ΔΫϥελΛௐͯ
let get_fat32clusters = ( fsinfo, head ) => { let
clusters = get_fat32clusters_reversed( fsinfo, head ); clusters.reverse(); let chunks = [ { 'at': clusters[ 0 ], 'length': 1 } ]; for( let i = 1; i != clusters.length; i++ ) { if( clusters[ i - 1 ] + 1 == clusters[ i ] ) { chunks[ chunks.length - 1 ].length++; } else { chunks.push( { 'at': clusters[ i ], 'length': 1 } ); } } return chunks; } ΫϥελνΣΠϯ ࿈ଓͨ͠Ϋϥελ·ͱΊͯ
let load_fat32cluster = ( fsinfo, chunks, index, data ) =>
{ let lba = fsinfo.clusters_lba + ( chunks[ index ].at - 2 ) * fsinfo.cluster_size; return read( lba, chunks[ index ].length * fsinfo.cluster_size ).then( result => { data.push( result.data ); if( index + 1 < chunks.length ) { return load_fat32cluster( fsinfo, chunks, index + 1, data ); } else return data; }); } ΫϥελνΣΠϯ READ(10)
let parse_fat32directory = ( data ) => { let files
= []; let lfn = []; for( let offset = 0; offset != data.byteLength; offset += 32 ) { let entry = data.subarray( offset, offset + 32 ); let attribute = entry[ 11 ]; let sfn_head = entry[ 0 ]; if( sfn_head == 0x00 ) { break; } else if( sfn_head == 0xE5 ) { continue; } else if( attribute == 0x0F ) { let fragment = new Uint8Array( 26 ); fragment.set( entry.subarray( 1, 11 ), 0 ); fragment.set( entry.subarray( 14, 26 ), 10 ); fragment.set( entry.subarray( 28, 32 ), 22 ); lfn.push( fragment ); σΟϨΫτϦΤϯτϦ ͦΜͳʹෳࡶ͡Όͳ͍ͷͰؾ߹͍Ͱύʔε͢Δ
ϒϥβͰUSBϑϥογϡϝϞϦΛಡΉΑ! http://fadis.github.io/webusb/storage/
USBϑϥογϡϝϞϦ͔ΒಡΜͩը૾Λ Webϖʔδʹදࣔ͢ΔΑ! http://fadis.github.io/webusb/storage/
WebUSBͷηΩϡϦςΟ WebUSBUSBσόΠεʹର͢Δ ͋ΒΏΔૢ࡞Λߦ͏ݖݶΛ ϦϞʔτ͔ΒඈΜͰདྷͨJavaScriptʹ༩͑Δ σόΠεʹΑͬͯյͤΔ σόΠεʹΑͬͯBadUSBʹͰ͖Δ
WebUSBͷηΩϡϦςΟ https://example.org/ ͳΜ͚ͩͲ USBσόΠεΛΘͤͯΑ https://example.com/ ͰΘΕΔҝʹ࡞ΒΕͨ WebUSBରԠσόΠεͰ͢ μϝ ॳWebUSBσόΠεʹใΛຒΊΔࣄͰ ҙਤ͠ͳ͍WebαΠτʹ
σόΠεΛΘΕͳ͍Α͏ʹ͠Α͏ͱͨ͠
ॳWebUSBσόΠεʹใΛຒΊΔࣄͰ ҙਤ͠ͳ͍WebαΠτʹ σόΠεΛΘΕͳ͍Α͏ʹ͠Α͏ͱͨ͠ ͕ɺॾൠͷཧ༝͔ΒఘΊͨ http://wicg.github.io/webusb/
WebUSBͷηΩϡϦςΟ σόΠεଓϓϩϯϓτҎ֎ʹ ѱҙ͋ΔυϥΠόͷ࣮ߦΛ͙ज़͕ͳ͍ WebUSB͕҆ఆ൛ͷChromeʹ࣮͞Εͨ https://developers.google.com/web/updates/2017/09/nic61
WebUSBͷηΩϡϦςΟ ͜ͷϓϩϯϓτͷҙຯ Ӿཡதͷ8FCϖʔδ͕ಘମͷΕͳ͍υϥΠόͰ 64#σόΠεΛ͖উख͢Δࣄʹಉҙ͠·͔͢
·ͱΊ WebUSBϒϥβͱUSBσόΠε͕ ձ͢Δ͜ͱΛՄೳʹ͢ΔAPIͰ͋Δ JavaScriptͰυϥΠόΛॻ͚ ͲΜͳUSBσόΠεͰಈ͔͢͜ͱ͕Ͱ͖Δ ͰϦϞʔτ͔Β߱ͬͯདྷͨ ಘମͷΕͳ͍υϥΠό৴༻Ͱ͖Δͷ͔
σόοά෩ܠ ͏·͘௨৴Ͱ͖ͳ͍࣌WiresharkͰUSBΛݟΑ͏