仙石浩明の日記

Android

2024年3月21日

突然死した Pixel4 から nanaco 残高を救い出した話

何の前触れもなく Pixel 4 が壊れた。 寝る前フツーに使っていたのに、 朝 7:00 ごろ見ると電源オフ状態になっている。 電源ボタンを長押しても何の反応もない。

単なるバッテリー切れ? と思って USB ケーブルをつないで充電してみる。 が、 画面は暗いまま。 電流を測ってみると 200mA ほど。 充電が行われているようには見えない。 もちろん USB の信号線にも何の応答もない。 「電源ボタン + ボリューム小」を長押し (強制再起動) しても無反応。

自宅の Wi-Fi ルータのログを見ると、 朝 6:46:06 に Pixel 4 から Google (*.1e100.net) に対して https アクセスをしたのが Pixel 4 からの最後の通信。 朝方までは正常に動いていたみたい。 自宅の Wi-Fi に接続しているスマホは、 その状態を監視するために、 定期的にサーバから ping を打っているが、 6:51:21 に打った ping には応答がない。 つまり起床直前、 06:46 〜 06:51 ごろ Pixel 4 が機能を停止したのだろう。

一週間ほど USB ケーブルをつないだまま放置したが、 ついに何も変化は起きなかった。 バッテリーや充電回路の問題というよりは、 ロジックボードの突然死が疑われる。 つまり分解してバッテリーを直接充電したとしても、 復活する望みは薄い。

機種変更できるアプリは、 速攻で他のスマホにインストールし直したが、 問題は おサイフケータイ。 Suica や nanaco には残高があったはず。

Suica は PC 等でモバイルSuica会員メニューサイトにログインして、 利用停止手続き を行うだけ。 10分後に新しいスマホに引き継げる。 翌朝 5:00 までは残高は 0円と表示されるが、 それ以降に確認したら残高が復活していた。

ところがセブン・カードサービス (nanaco の運営会社) は、 会員メニューサイトにログインするだけでは本人確認が充分でないと考えているらしい。 他人が (何らかの方法で窃取したパスワードを使って) 勝手にログインして利用停止手続きを行い、 残高を奪うことを恐れているのだろう。 まあ、nanaco のパスワードなんてテキトーに設定している人も多いだろうから、 その懸念は理解できなくもない。

nanaco の場合、 まず nanaco の機能停止を行った上で 「引継番号」 を発行し、 その「引継番号」を使って新しいスマホに nanacoモバイルアプリをインストールする。 ただし、 この段階では nanaco 残高は引き継がれない。 セブン・カードサービスから送られてくる 「nanaco引継申請書」に新しいスマホの nanaco番号を記入し、 本人確認書類 (マイナンバーカードなど) のコピーを添付して返送する必要がある。

この引継番号は、 機種変更の際の「引継番号」と同じものと思われるが、 機種変更の場合は残高が直ちに引継がれる点が異なる。

「引継番号」の発行までは以下のように WEB で手続きできる:

nanaco transfer 1

Suica のように会員メニューから故障 (or 紛失) したスマホの Suica の機能停止ができれば簡単なのだが、 nanaco の場合は、 氏名・生年月日・電話番号 (以下、「登録名義」と略記) を指定することで、 故障 (or 紛失) したスマホの nanaco (以下、「紛失nanaco」と略記) を特定するらしい。

つまり登録名義さえ分かれば、 他人が勝手に nanaco を機能停止できる? これって会員メニューサイトのパスワードを窃取するより簡単じゃない? 誕生日なんて facebook 等で公開している人が多いような… (ちなみに私は公開していない)

まあ、 紛失nanaco のユーザ全員が会員メニューにログインできるとは限らない (そもそもパスワードを忘れてしまっていたりする) から、 このような方式にしているのだろう。

More...
Filed under: Android — hiroaki_sengoku @ 08:31
2020年9月16日

IFTTT のアプレットが 3個に制限されてしまったので、IFTTT を使わずに スマート家電リモコン RS-WFIREX4 をコントロールしてみた 〜 RS-WFIREX4 の通信プロトコルの解析 hatena_b

IoT機器を IFTTT (IF This Then That) に登録すると、 自前のプログラムからコントロールできるようになる。 つまり IFTTT の webhooks を使うことで、 IFTTT の特定の URL を自前のプログラムからアクセスするだけで IoT機器がコントロールできる。 例えばこんな感じ:

senri:~ $ curl https://maker.ifttt.com/trigger/light_on/with/key/dD-v7GCx46LnWaF1AD9nwSUeA_N1ALvDHKS57cP1_Md
Congratulations! You've fired the light_on event

「light_on」の部分は任意に定めることができる。 この例では照明を点灯させている。

Nature Remo のように API を公開している IoT機器なら、 自前のプログラムから API を直接たたけばよいが、 残念ながら IoT機器の多くが API 非公開なので、 IFTTT が唯一のコントロール手段となっていた。 IoT機器の操作一つ一つ (例えば light_on) に、 IFTTT アプレットを作ることになるので、 私の場合は 50個以上のアプレットを作っていた。

Get IFTTT Pro

ところが!

有料版の IFTTT Pro が新たに発表され、 従来の無料版は登録できるアプレットが 3個に制限されてしまった。 有料版なら無制限にアプレットを作ることができるが、 無料版だと最大 3個しかアプレットを作ることができない。

かくなる上は、 IoT機器の API (通信プロトコル) を解析して IFTTT 抜きで IoT機器をコントロールするしかない。 いままでも IFTTT に登録できない IoT機器については API を解析していたので、 なんとかなるだろう。

IoT機器のほとんど (全て?) がスマホからコントロールできるので、 root 権限を取得できる Android 端末があれば、 スマホのアプリと IoT機器との間の通信を tcpdump 等で観察することができる。 通信が暗号化されていなければ API を解析するのは (比較的) 容易。

RS-WFIREX4

というわけで、 ラトックシステム社 スマート家電リモコン RS-WFIREX4 の API を解析してみた。 幸い、 RS-WFIREX4 の Android 用アプリ スマート家電コントローラ は、 家中モード (スマホと RS-WFIREX4 が同一セグメントにある場合) では通信が暗号化されていない。 スマホ上で tcpdump を実行することで通信 (TCP/IP) 内容を見ることができた。

RS-WFIREX4 は温度、湿度、明るさを計測することができる。 アプリが RS-WFIREX4 の TCP ポート 60001番に対して 5バイトのデータ 「AA 00 01 18 50」 を送信すると、 RS-WFIREX4 から 13バイトのデータ 「AA 00 09 18 00 01 5E 00 E5 00 0A B2 08」 が返ってきた。 最初の 5バイト 「AA 00 09 18 00」 は部屋の温度等に関係なく常に同一だったので、 ヘッダ (つまり計測データは含まれない) と考えられる。

この手の通信プロトコルでは、 可変長のデータを扱うためにヘッダにペイロード (ヘッダ以外のデータ本体) の長さが含まれる (ことが多い)。 この例ではヘッダ以外のデータの長さは 13 - 5 = 8 バイトなので、 おそらく 「00 09」 が (ビッグエンディアンの) ペイロード長だろうとあたりをつける。 つまりヘッダは 「AA 00 09 18」 の 4バイトで、 続く 9バイト 「00 01 5E 00 E5 00 0A B2 08」 がペイロードということになる。

┌─┬─┬─┬─┬─┐
│頭│ペイ長│測│検│
└─┴─┴─┴─┴─┘
│←─ヘッダ─→│ペ│

アプリが送信したデータ 「AA 00 01 18 50」 も、 先頭が 「AA」 (図中では 「頭」 と略記) であることから同じフォーマットである可能性が高い。 つまり 「00 01」 がペイロード長 (図中では 「ペイ長」 と略記) で、 「50」の 1バイトがペイロードなのだろう。 「50」は 「計測データを送信せよ」 という命令の可能性も無くはないが、 おそらく後述するチェックサムだろう (図中では 「検」 と略記)。

そして、 ヘッダ末尾の 「18」 のほうが、 「計測データを送信せよ」 という命令である可能性が高い (図中では 「測」 と略記)。 RS-WFIREX4 の応答のヘッダの末尾も 「18」 だが、 これは命令 「18」 に対する応答であることを示しているのだろう。

┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│頭│ペイ長│測│0│湿度%│温度℃│明るさ│安│検│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
│←─ヘッダ─→│←─────ペイロード─────→│

部屋を明るくしたり暗くしたり、 温度を上げたり下げたりしたときに、 RS-WFIREX4 からの応答がどのように変化するか調べることで、 ペイロードの 2, 3バイト目 「01 5E」 (10進数で 350) は、 「湿度 35.0 %」 を示していると推測できた。 以下同様に、 4, 5バイト目 「00 E5」 (10進数で 229) は、 「温度 22.9 ℃」 を、 6, 7バイト目 「00 0A」 (10進数で 10) は、 「明るさ」 を示している。

ペイロードの 8バイト目 「B2」 は、 RS-WFIREX4 の電源を入れた瞬間は 0 で、 時間の経過と共に増大し、 充分時間が経つと 255 になる。 RS-WFIREX4 は電源投入後 30分間はセンサーが使えないので、 おそらくセンサーの安定度合い (0〜255) を示していて、 この数値が一定以上 (255?) でないとセンサーの値が正確でないことを示しているのだろう。

末尾の 1バイトは後述するチェックサムと思われる。 なぜなら、 末尾の 1バイトを除くペイロードの内容が同じ (つまり湿度・温度・明るさ・安定度の組合わせが同一) 応答データであれば、 末尾の 1バイトも (少なくとも私が観察した範囲では) 同じ値になっているから。

AA 00 AE 11
00 00 AA
22 11 04 04 05 04 04 0D 04 0D 04 04 05 0C 05 04
04 04 05 04 04 0D 04 04 05 04 04 0D 04 04 05 0C
05 04 04 0D 04 04 05 04 04 0D 04 04 05 04 04 04
05 04 04 04 05 04 04 0D 04 0D 04 04 05 0C 05 04
04 04 05 0C 05 04 04 0D 04 04 05 04 04 0D 04 04
05 04 04 FF FF FF 07 23 10 05 04 04 04 05 0C 05
0C 05 04 04 0D 04 04 05 04 04 04 05 0C 05 04 04
04 05 0C 05 04 04 0D 04 04 05 0C 05 04 04 04 05
0C 05 04 04 04 05 04 04 04 05 04 04 04 05 0C 05
0C 05 04 04 0D 04 04 05 04 04 0D 04 04 05 0C 05
04 04 04 05 0C 05 04 04 04 05
69

次に赤外線を発射させて家電をコントロールしてみる。

← 左の 178バイトのデータをアプリが送信すると、 RS-WFIREX4 が発射した赤外線を受けて天井照明が点灯し、 RS-WFIREX4 から 6バイトの応答 「AA 00 02 11 00 D1」 が返ってきた。

送信データの最初の 4バイト 「AA 00 AE 11」がヘッダで、 ペイロードの長さが 00AE (10進数だと 174) であることが分かる。 ヘッダ末尾の 「11」 が、 「赤外線を発射せよ」 という命令なのだろう (図中では 「射」 と略記)。 アプリを操作して RS-WFIREX4 にいろいろ (長さが異なる) 赤外線を発射させてみたところ、 3行目 「22 11 04 04 ...」から始まる 170バイトが赤外線の波形データ (後述) で、 その直前 (2行目) の 「00 AA」 (10進数で 170) が赤外線の波形データの長さ (図中では「デー長」と略記) を表わしているようだ。

┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│頭│ペイ長│射│0│デー長│赤外線の波形データ│検│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
│←─ヘッダ─→│←─────ペイロード─────→│

ペイロード末尾の 1バイト 「69」 の算出方法は不明だが、 赤外線の波形データによって値が変わることと、 末尾であることからチェックサムのようなものと思われる。 もちろん単純なチェックサムではなく、 なんらかのエラー検出符号のようなものなのだろう (図中では 「検」 と略記)。 この 1バイトは、 「ペイロード長」には含まれるが、 「赤外線の波形データの長さ」には含まれない (12月18日追記: 「射」をヘッダではなくペイロードに含めて、その代わり「検」をペイロードから外すべきだった。後述)。

このペイロード末尾の 1バイトだけ異なるデータを送信すると RS-WFIREX4 はこの 178バイトの送信データ全体を無視する。 赤外線も発射しないし、 何の応答も返さない。 前述したように家中モードでは何のセキュリティもないので、 この末尾の 1バイトが (正規の) アプリから送信されたことを保証する唯一の認証手段なのだろう。 ここでは、 RS-WFIREX4 に対する送信データ (あるいは RS-WFIREX4 からの応答データ) におけるこの末尾の 1バイトを「チェックサム」と呼ぶことにする。

170バイトの赤外線の波形データは、 1バイト目の 「22」(10進数だと 34) が赤外線の ON の区間を表し、 2バイト目の 「11」が赤外線の OFF の区間を表し、 以下同様に奇数バイト目が赤外線の ON の区間を表し、 偶数バイト目が赤外線の OFF の区間を表す。 各バイトの数値の 1/10000 が各区間の秒数になる。 例えば 「04」 の場合は 0.4ミリ秒になり、 家製協(AEHA)フォーマットの 1T 区間に相当する。 1バイト目の 「22」は 8T 区間、 2バイト目の 「11」は 4T 区間、 8バイト目の 「0D」は 3T 区間に相当する。

IR wave

つまり赤外線の波形データの最初の行 (3行目) は、 赤外線 ON が 8T (3.2ミリ秒) 続き、 次に OFF が 4T (1.6ミリ秒) 続き、 以下 ON 1T, OFF 1T, ON 1T, OFF 1T, ON 1T, OFF 3T, ON 1T, OFF 1T, ON 1T, OFF 3T, ON 1T, OFF 1T という波形になる (上図 ↑)。 ただし ON の区間は赤外線が点きっぱなしになっているのではなく、 38kHz の赤外線パルス (デューティ比 1/3) を送信している。

この赤外線の波形データは、 アプリで 「リモコンデータ受け渡し⇒エクスポート⇒メールで送信」 を行うことで得られる XML データに含まれる (<code>...</code> の部分)。 あるいは他の学習リモコンの赤外線波形データを変換しても良い。

実を言うと、 ここまでは昨年の段階で解析済だった。 ペイロード末尾の 1バイトの算出方法が判明したら公開しようと思っていたのだが、 いろいろ他にも忙しくて :-) 放置してしまっていた (12月18日追記: 単なる CRC-8 だと判明。後述)。 アプリを逆コンパイルするのは骨が折れるのと、 算出しなくても tcpdump で見れば値が得られるので実用上は困らなかったから。 で、今回 IFTTT が有料化したので急遽公開することにした次第。

チェックサムの算出方法が分からないといっても高々 1バイトである。 256通りなんてブルートフォース攻撃というほど brute でもない。 幸い、 RS-WFIREX4 はチェックサムが違うデータを立て続けに受信しても、 異常動作することはないようだ (もちろん常時チェックサム違いのデータを送信することは推奨できない)。

赤外線の波形データごとに 00 〜 FF まで 256通りのチェックサムを試して、 RS-WFIREX4 から応答が返ってきたら、 赤外線の波形データにそのチェックサムを付加して記憶しておけばよい。 赤外線の波形データの前の 3バイトおよびヘッダは、 発射する際に都度算出すれば良い。

チェックサムが正しい場合、 RS-WFIREX4 は赤外線を発射して 「AA 00 02 11 00 D1」 を返す。 ヘッダ末尾の 「11」 は、 命令 「11」 (赤外線を発射せよ) の応答であることを示す。

┌─┬─┬─┬─┬─┬─┐
│頭│ペイ長│射│0│検│
└─┴─┴─┴─┴─┴─┘
│←─ヘッダ─→│←ペ→│

ペイロードは 「00 D1」 の 2バイトだが、 ペイロードが 2バイト以上の場合、 ペイロードの先頭は常に 「00」 であるようだ (図中では 「0」 と表記)。 末尾の 「D1」 はチェックサムだろう。 実質的にチェックサムだけのペイロードならば、 1バイトのペイロードで充分だと思うが、 アプリが送信するデータの場合は 1バイトのペイロードが有り得ても、 RS-WFIREX4 が返す応答データの場合は常に 2バイト以上になるのかもしれない。

RS-WFIREX4 をコントロールする perl スクリプト wfirex.pl を以下に示す:

#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Std;
use IO::Socket::INET;

my %Ir;
$Ir{'on'} = pack("H*", "221104040504040D040D0404050C050404040504040D04040504040D0404050C0504040D04040504040D040405040404050404040504040D040D0404050C05040404050C0504040D04040504040D0404050404FFFFFF07231005040404050C050C0504040D040405040404050C05040404050C0504040D0404050C05040404050C050404040504040405040404050C050C0504040D04040504040D0404050C05040404050C050404040569");
$Ir{'off'} = pack("H*", "221005040404050C050C0504040D040405040404050C05040404050C0504040D0404050C05040404050C0504040405040404050C050C050C050C0504040D040405040404050C050C05040404050C0504040405FFFFFF07221104040504040D040D0404050C050404040504040D04040504040D0404050C0504040D04040504040D0404050404040504040D040D040D040D0404050C050404040504040D040D04040504040D040405040437");
$Ir{'small'} = pack("H*", "221104040405040D040D0404040D040504040504040D04040405040D0404050C0504040D04040504040D04040405040404050404050C050C050C0504040D04040405040D040D040D04040504040D0404040504FFFFFF08221104040405040D040D0404050C040504040405040D04040405040D0404050C0405040D04040405040D04040504040404050404040D050C050C0504040D04040504040D040D040D04040405040D0404040504E9");
$Ir{'aoff'} = pack("H*", "211005040404040d0404040d040404040504040c050c040405040404040d040c0405040404040504040405040404040405040404050404040404050c0404050404040405040404040504040c0504040404050404040d040404040504040404050404040c0504040c050c040d040c040d040c04d7");
$Ir{'study_on'} = pack("H*", "2210050C04050404040405040404050404040405040404050404040D0404040504040405040404050404040D040D040404050404040504040405040C0504040D0404040D040D040C050C0405040C0504040D04040405040404050404040405040404050C050C040D040C050C050C040D040D0404050C040405040404050404040504040C0504040D040C050C050C040D040D0404040D0404050404040504040C050C050C0405040C050C040D040D0404040504FF2E2111040C05040405040404050404040404050404040504040405040C0504040404050404040504040405040D040C040504040405040404050404040D0404050C0405040C050C040D040D0404040D0404050C04050404040504040404050404040504040D040C050C040D040D040C050C040D0404050C040504040405040404050404040D0404040D040D040D040C050C050C0405040C0504040404050404040D040D040C0504040D040C050C050C0405040404FF292111040D04040405040404050404040504040405040404050404040D0404040504040405040404050404040D040D040404050404040504040404050C0405040C0504040D040C050C040D0404050C0405040C05040404050404040504040404050404040D040D040C050C050C040D040C050C0504040D040404040504040405040404050C0504040C050C050C040D040D040C0504040D0404040504040404050C040D040D0404050C040D040C050C05040404042F");
$Ir{'study_off'} = pack("H*", "2210040D04050404040504040405040404040504040404050404040D0405040404050404040503050404040D040D040404050404040504040405040C0504040D0404040D040D040C040D0405040C0504040D04040405040404050404040504040405040C050C040D040D040C050C040D040D040C050C04050404040504040405040404050404040D040D040C050C040D040D0404040D0405040404040405040D040D040C0405040C050C040D040D0404040504FF2E2111040D04040405040404050404040504040405040404050404040D0404040504040405040404050404040D040C050404050305040404050404040D0405040C0504040D040C050C040D0404050C0405040C05040404050404040405040404050404040D040D040C050C040D040D040C050C040D040D04040504040405040404050404040504040C050C050C040D040C050C0504040D0404040504040404050C040D040D0404040D040D040C050C0504040404FF292210050C04050404040504040405040404050404040404050404040D0405040404050404040404050404040D040D040404050404040504040405040C0504040D0404040D040D040C050C0405040C0504040D04040405040404050404040405040404050C040D040D040C050C040D040D040D040C050C04050404040405040404050404040504040D040C050C040D040D040D0404040D0404040504040405040D040C040D0405040C040D040D040D040404050408");
$Ir{'study_aoff'} = pack("H*", "2c2b070f0610060f061006050605060f060506050605060406050610060f0605061006050604060506050605060505100610060f06100610060f0610060f060506050605060506040605060506050605060f06100605060f0610060506040605060506050605050506050605061005100610060506040605060506050605050506050610060505100610060505100610060505332d2b0610060f0610061005050605061006050505060506050605060f06100605060f0605060506050605050506050610060f0610061005100610060f06100605060506040605060506050605050506050610060f06050610060f06050605060506050505060506050605060505100610060f06050605060506050505060506050605060f06050610060f06050610060f06050675");

our ($opt_v, $opt_s);
getopts('vs') || help();
my $ip = shift || help();
my $command = shift || help();

if ($command eq "get") {
    my ($il, $te, $hu) = get_wfirex($ip);
    if (defined $il) {
        $te /= 10;
        $hu /= 10;
        print "il=$il te=$te hu=$hu\n";
    } else {
        print "wfirex get TIMEOUT $ip\n";
    }
} elsif (defined $Ir{$command}) {
    my $ret;
    if ($opt_s) {
        my $ir = substr($Ir{$command}, 0, length($Ir{$command})-1);
        for (my $i=0; $i < 256; $i++) {
            my $checksum = pack("C", $i);
            printf("try %02x ...\n", $i);
            $ret = send_wfirex($ip, $ir . $checksum);
            if ($ret) {
                printf("success ! checksum=%02x ret=%02x\n", $i, $ret);
                last;
            }
            sleep 1;
        }
    } else {
        $ret = send_wfirex($ip, $Ir{$command});
    }
}
exit 0;

sub get_wfirex {
    my ($ip) = @_;
    my $sock = IO::Socket::INET->new(
        PeerAddr  => $ip,
        PeerPort  => 60001,
        Proto     => "tcp",
        Timeout   => 5,
        );
    if ($sock) {
        print $sock "\xaa\x00\x01\x18\x50";
        my $buf;
        my $flags;
        $sock->recv($buf, 256, $flags);
        my @data = unpack("CCCCCnnnCC", $buf);
        close($sock);
        print join(" ", @data) . "\n" if $opt_v;
        return ($data[7], $data[6], $data[5]);
    }
    return undef;
}

sub send_wfirex {
    my ($ip, $ir) = @_;
    my $len = length($ir) - 1;
    $ir = "\xaa" . pack("n", $len+4) . "\x11\x00" . pack("n", $len) . $ir;
    my $sock = IO::Socket::INET->new(
        PeerAddr  => $ip,
        PeerPort  => 60001,
        Proto     => "tcp",
        Timeout   => 5,
        );
    if ($sock) {        
        print $sock $ir;
        my $buf;
        my $flags;
        $sock->recv($buf, 256, $flags);
        my @data = unpack("CCCCCC", $buf);
        close($sock);
        if (@data) {
            print join(" ", @data) . "\n" if $opt_v;
        }
        return $data[5];
    }
    return undef;
}

sub help {
    print <<EOF;
Usage: wfirex <opt> <IP> <com>
opt:   -v   ; verbose
       -s   ; scan checksum
com: get    ; get sensor value
EOF
    print "     " . join(" ", sort keys %Ir) . "\n";
    exit 1;
}

連想配列 %Ir に赤外線の波形データを 16進数の文字列で格納しておく。 末尾の 2文字 (つまり 16進数 1バイト) がチェックサム。 チェックサムが不明のときは、 とりあえず「00」をつけておいて「-s」オプションでチェックサムを探索する。

senri:~ $ ./wfirex -vs wfirex4l on
try 00 ...
try 01 ...
try 02 ...
try 03 ...

  … 中略 …

try 67 ...
try 68 ...
try 69 ...
170 0 2 17 0 209
success ! checksum=69 ret=d1

赤外線の波形データ $Ir{'on'} のチェックサムが 「69」 (16進数) であることが判明したので、 とりあえずつけた末尾の 「00」 を 「69」 で置き換える (前掲のスクリプトは置き換え済)。

More...
2019年11月28日

IFTTT に登録できないのでお蔵入りになってた Eco Plugs RC-028W & CT-065W が、UDP パケットを送るだけでコントロールできた!

IoT 機器の多くが、 専用のスマホアプリだけでなく Googleアシスタントや Amazonアレクサからコントロールできる。 しかし、 いちいち音声でコントロールするのはメンドクサイ (なぜ音声以外の方法でもコントロールできるようにしないのか?)。 出かけるときに毎回 「行ってきま〜す」 などと Googleアシスタントに呼び掛けるのは、 いかがなものかと思う。 外出を勝手に検知して家電を適切にコントロール (例えば電気ポットの電源を切る) してくれるほうがずっといい。

IoT 機器を IFTTT に登録すると、 自前のプログラムからコントロールできるようになる。 IoT 機器は Googleアシスタントでコントロールするより、 自前のプログラムでコントロールするに限る。 例えば自宅の Wi-Fi LAN にスマホが繋がっているかプログラムで監視し、 繋がってるスマホがいなくなったら留守になったと判断して、 自動的に電気ポットの電源を切れば、 電気ポットのコンセントを抜いたかどうか出先で心配せずに済む。 あるいはコンセントを抜くのを忘れて寝てしまい、 翌朝電気ポットのお湯が熱いままなのを見て愕然とするより (先月の電気使用量が 600kWh だったので驚いた)、 部屋が暗いときは自動的に電源が切れている方がいい (これはプログラムを書かなくても IFTTT だけで実現できる)。

というわけで持ってる IoT 機器を片っ端から IFTTT に登録したのだけど、 IFTTT に登録できない IoT 機器も残念ながら若干ある。 いまどき IFTTT に登録できない IoT 機器に何の意味があるのだろう? (今なら絶対に買わない) と思うのだけど、 IFTTT の便利さを知る前に買ってしまったのだから後悔先に立たず。 IFTTT の便利さを知ってからは、 お蔵入りになっていた。

EcoPlugs RC-028W

Eco Plugs もそんな「使えない」IoT 機器の一つ。 当時としては安価だった (今ならもっと安い) ので Walmart で購入してしまった。 Googleアシスタントや Amazonアレクサには登録できるのに、 肝心の IFTTT に登録できない。

といって通信プロトコルを解析しようにも、 いまどきの IoT 機器はクラウド (ベンダが運用するサーバ) と https で通信するので調べる取っ掛かりがない。 最後の手段、 分解するしかないのか?

ところがググっていると、 Eco Plugs は平文で通信しているという投稿を見つけた。 Eco Plugs はクラウドに登録しなくても、 同一 LAN セグメントならスマホアプリでコントロールできるが、 同一 LAN 内では平文の UDP パケットを飛ばしているらしい。

ありがたいことに Eco Plugs をコントロールする JavaScript プログラムが GitHub に公開されていた。 JavaScript は文法もロクに知らない (^^; のだけど、 見よう見まねで perl で書き直してみる:

#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Std;
use IO::Socket::INET;
our ($opt_v);
(getopts('v') && @ARGV == 3) || &help;
my ($ip, $id, $on) = @ARGV;

my $state = 0x0100;
$state = 0x0101 if $on eq "on";
my $buf = pack("H260", 0);
# Byte 0:3 - Command 0x16000500 = Write, 0x17000500 = Read
substr($buf, 0, 4) = pack("N", 0x16000500);
# Byte 4:7 - Command sequence num - looks random
substr($buf, 4, 4) = pack("N", rand(0xffffffff));
# Byte 8:9 - Not sure what this field is - 0x0200 = Write, 0x0000 = Read
substr($buf, 8, 2) = pack("n", 0x0200);
# Byte 16:31 - ECO Plugs ID ASCII Encoded - <ECO-xxxxxxxx>
substr($buf, 16, 16) = $id;
# Byte 116:119 - The current epoch time in Little Endian
substr($buf, 116, 4) = pack("L", time());
# Byte 124:127 - Not sure what this field is - this value works, but i've seen others 0xCDB8422A
substr($buf, 124, 4) = pack("N", 0xCDB8422A);
# Byte 128:129 - Power state (only for writes)
substr($buf, 128, 2) = pack("n", $state);

my $sock = IO::Socket::INET->new(PeerAddr => $ip, PeerPort => 80,
    Proto => 'udp', Timeout => 1) || die;
my $flags;
print unpack("H*", $buf) . "\n" if $opt_v;
print $sock $buf;
$sock->recv($buf, 1024, $flags);
print unpack("H*", $buf) . "\n" if $opt_v;

# Byte 10:14 - ASCII encoded FW Version - Set in readback only?
my $fwver = substr($buf, 10, 5);
# Byte 48:79 - ECO Plugs name as set in app
my $name = substr($buf, 48, 32);
$name =~ s/\0*$//;
printf("%s (ver %s)\n", $name, $fwver);

sub help {
    print <<EOF;
Usage: ecoplugs <opt> <IP> <ID> <on/off>
opt:   -v           ; verbose
EOF
    exit 1;
}

長さ 130 バイトの UDP パケット (変数 $buf) を作って Eco Plugs へ送信している (print $sock $buf;) だけなので、 いたってシンプル。 ユーザ認証もないので LAN 内なら誰でもコントロールできる。

Eco Plugs の IP アドレス (第1引数) と、 Eco Plugs の ID 「ECO-XXXXXXXX」(第2引数, XXXXXXXX は MACアドレスの第3〜6オクテット, ただし 16進数の A〜F は大文字限定)、 および「on」あるいは「off」の 3引数を付けて、 この perl プログラムを実行すると、 該当 Eco Plugs をオン/オフし、 Eco Plugs の名前 (スマホアプリで設定できる。以下の例では 「potplug」) と、 ファームウェアのバージョン (以下の例では 「1.6.3」) を表示する。

senri:~ $ ecoplugs -v 192.168.15.123 ECO-01234567 on
16000500940c163b020000000000000045434f2d303132333435363700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a625df5d00000000cdb8422a0101
160005000000163b0000312e362e330045434f2d30313233343536370000000000000000000000000000000000000000706f74706c7567000000000000000000000000000000000000000000000000003031323334353637000000000000000000000000000000000000000000000000a8e23b7ea625df5d00000000cdb8422a
potplug (ver 1.6.3)

「-v」オプションを付けた場合、 最初に表示される行が Eco Plugs へ送った長さ 130バイトの UDP パケット (260桁の 16進数)、 2行目が Eco Plugs から返ってきた長さ 128バイトの UDP パケット (256桁の 16進数)。 第1引数で指定した IP アドレスが Eco Plugs のものでなかった場合、 あるいは第2引数で指定した ID が間違っている場合など、 応答が返ってこない時は待ち続ける。 ID の 16進数において A〜F が小文字だと応答しないので注意。

Eco PlugsRC-028W (屋外用) および CT-065W (屋内用) で動作を確認したが、 おそらく同シリーズの他の機器でも使えるだろう。 Woods の WiON (スマホアプリが Eco Plugs そっくり) でも使えるらしい。

More...
2017年8月11日

ZenFone3 で撮影した写真ファイル内の、位置情報が壊れている Exif データを修復するコードを書いてみた

昨年発表された ASUS のハイエンド スマホ ZenFone 3 Deluxe ZS570KL の購入時の OS は Android 6.0 Marshmallow だが、 この OS (UL-Z016-WW-4.12.40.1698) の標準のカメラ アプリには、 撮影した写真の位置情報が他のアプリで参照できないというバグがあった。

つまり、 カメラの設定で 「場所サービス」 を 「オン」 にすると、 撮影した場所 (GPS などの位置情報) を写真ファイルに付加する (Exif データ) が、 この位置情報に問題があって、 他のアプリで写真を見ても位置情報が表示されない。 例えば撮影した写真を Google フォトへアップロードしても、 撮影場所が表示されない

browsed with Gallery Application

この写真を Google フォトからダウンロードしてみれば、 Exif データ内に位置情報が保持されていることが分かるし、 アプリによってはこの位置情報を参照できるものもある。

例えば OS 標準の 「ギャラリー」 アプリで見ると、 位置情報が正しく保持されているように見える。 例えば、 御成町の交差点で撮影したこの写真を 「ギャラリー」 で見ると (右図 ⇒ ZenFone のスクリーンショット)、 場所が 「鎌倉市, 御成町」 と正しく表示される。

位置情報を正しく表示できるアプリがある以上、 バグではなく仕様 (Exif のバージョンの違い?) の可能性もある。 このとき私は、 この写真の位置情報を参照できないアプリは、 アプリ側にも問題があるのだろうと思ってしまった。 おそらく開発元でも同様の理由で、 このバグが見逃されてしまったのだろう。

More...
Filed under: Android,プログラミングと開発環境 — hiroaki_sengoku @ 22:09
2017年7月26日

カナダで Project Fi を使ってみた 〜 現地でプリペイドSIMを買うより安いし、同時に複数のスマホで使える hatena_b

昨年に続いて今年も、 日本の梅雨 (と夏の暑さ) から逃れるべく バンクーバーに滞在した昨年の滞在では fido, WIND, Virgin のプリペイド SIM を買ったが、 今年 1月に米国を訪れたとき Project Fi を契約したので、 Project Fi の SIM をバンクーバーでも使ってみた。

Project Fi は米国外で使っても (米国内で使う場合と同じく) データ通信が $10/GB (1MB あたり 1セント) という、 リーズナブルかつシンプルな料金体系。 カナダだと、 何故かキャリアのプリペイド SIM を買うより安くつく。 カナダでは Telus や Bell の通信設備を使ってローミングサービスしているようだが、 Telus や Bell のプリペイド SIM よりも料金が安いのは何故なのか?

バンクーバーのいたるところにキャリアの店舗があるので、 適当に入ってみた店で、 「いま Google の Project Fi を使っているのですが、 Project Fi より安くなるプランありますか?」と (もちろん英語で) 聞いてみたら、 「Project Fi みたいに安いプランはありません!」 と即答されてしまった。(^^;

しかも、 Project Fi の大きな特徴として、 追加料金無しで Data-only SIM を 9枚まで追加できる。 妻のスマホに Data-only SIM を入れておけば、 Project Fi 一契約だけで (私と妻、二人分の) 用が足りてしまう。 Data-only SIM を 9枚入手すれば、 10人で共有できる?

なお、 Project Fi の (通話可能な) SIM は、 Pixel や Nexus に入れて使うことが推奨されているが、 ふつうのスマホに入れても通話やデータ通信が可能。 私はバンクーバー滞在中ずっと、 ZenFone 3 Deluxe (ZS570KL) に入れていた。 もちろん Pixel も持ってはいるのだが、 ZenFone のほうがメモリが多く、 また root 権限を取得済なのでいろいろ使い勝手が良い。

もちろん、 いくら安くても通信速度が遅ければ意味がないので、 その時は現地でプリペイド SIM を買うつもりでバンクーバーを訪れたのだが、 Project Fi でもしっかり LTE の電波をつかんでくれた。 昨年使った fido や Virgin のプリペイド SIM と比べてなんら遜色がない (WIND は 3G のみ)。 結局 27日間のバンクーバー滞在で、 一度も現地 SIM を買おうという気にはならなかった。

Project Fi のページで確認すると、 7月の 24日間のデータ通信量が 7.36GB (私が 2.62GB、妻が 4.75GB) だった。 ホテルの Wi-Fi が使い物にならないほど遅かったので、 ホテルに居るときまで Project Fi を使うことになり、 ふだん日本で 1ヶ月に使うデータ通信量より多くなってしまった。

最近のアプリ (特に facebook, twitter, Instagram など) は、 画像や動画をストレスなくどんどん表示するために、 勝手にどんどん通信するのでいかがなものかと思う (私はこの手の通信しまくりアプリは一切インストールしない)。 滞在したホテルは 「無料 Wi-Fi 完備」 をうたっていたが、 宿泊客がこの手の通信しまくりアプリを使うためか、 夕方以降は全くといっていいほど通信できなくなってしまっていた。

日ごとのデータ通信量は次のような感じ:

Project Fi Current cycle

日によってばらつきが大きいが、 平均 300MB くらい。 二人分なのでこんなもの? 7.364GB で $73.64 (約 8250円)。 国際電話の通話料が 21分間で $4.20 (約 470円)。 さらに Project Fi の基本料金 $20 と税金等 ($6 くらい?) がかかる。 まだ請求が来ていない (〆日は 8月2日) が、 合計 $104 くらい?

Project Fi で、 カナダから米国の toll free 番号に電話できるのか? と思いつつ 1-888 から始まる電話番号に電話したら繋がったので、 てっきり無料通話かと思って安心して長電話してしまった。 有料なら繋がらないで欲しい (>_<)。 繋がらなければ、 米国外からかけるときに使えるコレクトコール番号を使ったのに。
1分あたり 20セントという国際電話料金は、 ケータイの通話料の感覚からすると安いが、 Google Voice の感覚からすると高い。 ZenFone 3 に Project Fi の SIM を入れて通話したのだが、 Pixel (あるいは Nexus 5X) に入れて通話すれば無料だったのかも?
少なくともハングアウトで発信していれば無料だった。

昨年のバンクーバー滞在は 14日間と今年の半分の日数だったが、 fido の SIM に CA$89.60 (約 7900円)、 Virgin の SIM に CA$72.79 (約 6400円)、 WIND (現 Freedom Mobile) の SIM に CA$72.80 (約 6400円) 支払った。 もし今回 Project Fi がなくて昨年と同様にプリペイド SIM を買っていたら、 昨年の倍、4万円近くかかってしまったかもしれない。

カナダでは、 どのキャリアも 1GB 程度使おうとすると、CA$70 以上かかってしまう。 各キャリア横並びの料金で、 格安SIM 登場前の日本みたいな感じ。 キャリア間の競争がないのかも? どの月額プランを選ぶかでデータ通信の課金レートが変わってくるので、 最適な月額プランを選ぶのが難しい。

これに対し Project Fi は、 どんなに使っても 1MB あたり 1セントの単一課金レートなので分かりやすいし、 課金レートだけ比較すると、 カナダのどのキャリアよりも安い (例えば比較的安価な Virgin でも 2¢/MB)。

ただし Project Fi はプリペイド SIM ではなくポストペイ SIM なので、 米国に住所がないと契約が難しいかも? 私の場合はホテルの住所で契約することができた。 私は米国の銀行が発行したクレジットカードで払っているが、 日本のクレジットカードで契約できるかどうかは不明。

また、ポストペイなので当然だが、 使わなくても毎月基本料金 ($20 と税金等が $3 ほど) がかかる。 日本でもローミングサービスが利用できるが、 日本だと 1¢/MB という課金レートは (格安 SIM と比べると) 安いとは言えず、 常用には向かない。

日本に居るときは基本料金が無駄になるなぁ、 $23 というのは無駄に払う額としては安くないなぁ、 と思いつつ (今年 1月に) 契約したのだが、 実は Project Fi の契約は一時停止できる。

一時停止中は、 データ通信ができないのはもちろん、 通話の着信もできなくなるが、 いまどき通話なんてできなくても困らない。 Google Voice の番号は他にも持ってるし。 :-)

というわけで今年 3月1日に一時停止した。 停止/再開は Project Fi の Web ページからいつでも可能。 3ヶ月後に自動で再開してしまうが、 その直後に停止すれば基本料金はほとんどかからない。 3ヶ月後の 5月30日に自動再開したが、 6月4日に停止し、 バンクーバーを訪れる直前 6月27日に再開し、 日本に帰国してから停止した。 自動再開した直後に停止することさえ忘れなければ (自動再開を知らせるメールが届くので見落とさなければ大丈夫)、 使っていない期間の基本料金をほぼゼロにできる。

More...
Filed under: Android,SIM — hiroaki_sengoku @ 21:48
2017年4月19日

ZenFone3 Deluxe を Nougat 7.0 にしたらデータ通信できなくなった 〜 アンロックして Android 6 に戻す 〜 hatena_b

Nexus 5X が突然壊れて以来、 私はメインのスマホとして ZenFone 3 Deluxe ZS570KL を使っている。 購入時の OS は Android 6.0 Marshmallow だったが、 今年 3月にシステム更新のお知らせが表示されたので、 更新を行なったら Android 7.0 Nougat WW-5.14.44.1898 になった (3月26日)。

ところが!

この更新によって通話もデータ通信もできなくなってしまった。 LTE ネットワークへ接続できなくなっていて、 アンテナ・ピクトも表示されない (バツ印が付く)。 最初 SIM の接触不良を疑ったのだが、 SIM を入れ直しても接続できないまま。

更新作業は Wi-Fi のあるところ (自宅) で行なうわけで、 (モバイル) データ通信できなくても Wi-Fi でデータ通信できるから気付きにくい。 まさか更新で通話やデータ通信ができなくなってしまっているとは思わないので、 翌日 (3月27日) も気付かずに出かけてしまい、 出先でデータ通信できないことに初めて気付いて往生した。 出かける前に気付いていれば、 昔のスマホ (Nexus 5X とか) に SIM を入れ替えて使うこともできただろうに。

LTE で接続できないなら、 W-CDMA で接続してみてはどうだろう?と思ったので、 設定メニューで 「モバイルネットワーク」 を選び、 「有線ネットワークタイプ」 を 「2G/3G/4G」 から 「2G/3G」 へ変更 (つまり LTE 接続を抑制) してみた。 すると無事 3G (W-CDMA) ネットワークへ接続して (アンテナ・ピクトが表示されて) 通話ができるようになった。 しかし 3G でもデータ通信はできない。

もちろん、 SIM を別のスマホに入れると何の問題もなく通話もデータ通信もできるので、 SIM や設定の問題ではなく、 Nougat な ZS570KL の問題。

この時点では ZS570KL の root 権限を取得していなかった (まだブートローダをアンロックしていなかった) ので、 できることは限られている。 仕方ないので、 設定メニューの 「バックアップとリセット」 で 「データの初期化」 を行なってみると、 LTE へ接続できるようになった (3月28日)。

Android のバージョンを上げるたびに初期化を強要されてはかなわないと思いつつも、 いちおうこれで一件落着なのだが、 せっかく初期化したのだから、 この機会に (自分流にカスタマイズする前に) ブートローダをアンロックすることにした。

昨年 9月11日に発売開始した ZS570KL だが、 アンロックの方法を ASUS が公開したのは半年近くが経過した 2月16日になってから。 アンロックツール ZS570KL_UnlockTool_V1.1.zip が ASUS の Driver & Tools ページからダウンロードできる。

ASUS UnlockTool

アンロックを行なうとデータの初期化が行なわれてしまうので、 アンロックするならカスタマイズする前の方がいい。 本当は購入直後にアンロックするのが一番なのであるが、 当時はアンロックツールが (少なくとも公式には) 公開されていなかったので、 いままでアンロックせずに使い続けてきた次第。

ASUS のページからダウンロードした zip ファイルを展開して得られた UnlockTool-9.0.0.29_ZS570KL.apk を ZS570KL へインストールして実行すると、 警告画面 (→ 右図) が表示される。

アンロック (ロック解除) すると一切の保証が無くなるだけでなく、 ソフトウェアのアップデートも受けることができなくなると書いてある。 もちろん、 同ページから最新のソフトウェアをダウンロードすること自体は (ロック解除とは関係なく) 可能なので、 ここで言う 「ソフトウェアのアップデート」 とは、 あくまで自動で行なわれる更新のことだろう。

「押してデバイスのロック解除を行う」 を押すと、 再起動と初期化が行なわれる。 これでブートローダがアンロックされ、 ボリューム大ボタンを押しながら電源を入れると、 ブートローダ・モードへ入ることができる。

Nexus 等のブートローダ・モードは、 起動する OS を選べたりするが、 ZS570KL のブートローダ・モードは、 通常の起動画面 (ASUS ロゴ) と変わらず、 ブートローダ・モードに入ってるか外見上は区別できないので注意が必要。 ZS570KL と USB ケーブルでつないだ PC 上で fastboot コマンドを実行することで、 任意のブート・イメージ (TWRP など) を起動したり、 フラッシュメモリへ書込んだりすることが可能。

きちんと動作する ZS570KL 用の TWRP がちょうど公開されていた (3月19日, これ以前のバージョンは画面が逆になるなど、いろいろ不具合があった) ので、 ZS570KL をブートローダ・モードにした上で、 fastboot コマンドを使って起動してみる:

$ fastboot boot twrp-3.1.0-0-Z016D-20170319-N.img 
downloading 'boot.img'...
OKAY [  0.777s]
booting...
OKAY [  0.394s]
finished. total time: 1.171s
$ 

ちょっと使ってみた限りでは、 問題無く使えるようだ。 早く TWRP 公式ページからダウンロードできるようになることを望む。 TWRP を ZS570KL の recovery パーティションへ書込んでおくと、 ボリューム小ボタンを押しながら電源を入れることで、 (PC とつながなくても) TWRP を起動できる。

$ fastboot flash recovery twrp-3.1.0-0-Z016D-20170319-N.img 
target reported max download size of 536870912 bytes
sending 'recovery' (19980 KB)...
OKAY [  0.777s]
writing 'recovery'...
OKAY [  0.021s]
finished. total time: 0.797s
$ 

アンロックしたらついでに root 権限も取得したくなるのが人情なので、 SuperSU v2.79 SuperSU-v2.79-201612051815.zip を TWRP からインストールした。

と、 ここまでは順調だったが、 ある日 (4月4日) 出先で唐突にデータ通信できなくなった。 外出中にデータ通信できなくなると往生する (前述したように 3月27日に体験済み) ので、 こんなこともあろうかと ZS570KL には普段使っている IIJmio の nano SIM の他に、 非常用として Aeon Mobile の micro SIM も入れていた。 電話の着信は両方の SIM で可能 (DSDS, Dual SIM Dual Standby) で、 電話の発信とデータ通信は、 どちらの SIM で行なうか選ぶ方式。 データ通信を Aeon Mobile へ切り替えると、 無事データ通信できた。

なぜ Aeon Mobile ではデータ通信できるのに、 IIJmio ではできないのだろう? と思いつつも、 IIJmio でもアンテナ・ピクトは表示されるし、 IIJmio の電話番号に着信できるので、 そのまま使い続けた。

もちろん、 Aeon Mobile なら他の SIM でもデータ通信できるのか?とか、 micro/nano どちらの SIM トレイに入れるかでデータ通信の可否が変わるのか?とか、 いろいろ確認したいことはあり、 その都度いろんな実験をしている。 が、結論から言えばいずれも直接の関係は無さそうだった。 わざわざ他キャリアから Aeon Mobile へ MNP して新たに nano SIM を作ってみたがデータ通信できなかったし、 IIJmio や mineo の nano SIM にゲタを履かせて micro SIM トレイに入れてもデータ通信できるようにはならなかったし、 nano SIM トレイでも例えば香港3 の nano SIM なら (ローミングで) データ通信できた。

そんなある日 (4月13日)、 IIJmio の電話番号へ電話がかかってきたので出たら、 音声が互いに聞こえなかった。 驚いて Aeon Mobile で通話実験したら、 Aeon Mobile でも着信/発信ともに音声が聞こえなくなっていた。

少なくとも 4月11日までは普通に通話できていたので、 2日間の間に何があったか思い出そうとしたが全く心当たりがなかった。 ある日突然、通話できなくなるなんてことが起きると大変困る。

# 4月21日追記: TWRP で DSP をリストアすると発症するらしい。
# 音声が聞こえない症状については Nougat への更新とは無関係だった。

データ通信ができないのはモバイル・ルータを持ち歩くことで対処できるが、 通話ができないのでは話にならない。 直ちに ZS570KL の使用を中止し、 IIJmio SIM を Pixel に入れて Pixel を持ち歩くことにした。 Pixel は Band 19 をサポートしていないので docomo (の MVNO) では使いにくく、 (日本では) あまり使っていなかった。

というわけで ZS570KL の使用を中止したので、 さらに思いきった実験ができるようになった。 手始めに再度初期化してみることにした。 今回は root 権限を取得済なので、 バックアップ/リストアが手軽にできる。 初期化してデータ通信が復活すれば、 徐々に設定を変えていくことで原因の切り分けも可能だろう。

ところが!

初期化したのに IIJmio のデータ通信は復活しなかった (4月14日)。 通話も着信/発信はできるけど音声が伝わらない。 ZS570KL 単体の問題ではなくて、 LTE ネットワークとの相性も関係あるのか? ますます何が原因なのか分からなくなってしまった。

初期化ではなく Nougat をクリーン・インストールすれば、 通話もデータ通信も元通りできるようになるのかもしれない。 しかしいつ同じ症状が再発しないとも限らない。 突然データ通信できなくなったり、 音声が聞こえなくなったりするようでは安心して使えない。 しかもこの Nougat にはメニューボタンが使えないという (私にとっては) 重大なバグもある (本来は 「ASUS カスタマイズ設定」 でマルチタスクボタンの長押しでメニューを表示するように設定できるはずだが、 この Nougat では機能しない)。

通話やデータ通信ができなくなるのはおそらくバグだろうし、 メニューボタンのほうは明らかにバグ。 ついでに言うと、 カメラが EXIF に記録する位置情報が壊れていて、 他のソフト (Googleフォトを含む) で位置情報が認識できないというバグもある。 そのうち修正版が公開されるのだろうと思っていたが、 Android 7 Nougat WW-5.14.44.1898 が公開されてから一ヶ月がたつのに何も発表されない。 ASUS はバグを認識していないのか?

# 4月19日追記: WW-5.14.44.2096 が公開された。が、症状は変わらず (>_<)

事ここに至っては Nougat の使用を諦め、 Android 6 Marshmallow に戻すしかないと決断した。 といっても、 バージョンを戻す方法は (ASUS 公式には) 提供されていない。 が、 アンロックした ZS570KL であれば、 フラッシュメモリに直接書込めるので、 Marshmallow のイメージを書込めばよい。 つまりクリーン・インストール。

このブログを書いていて (いまごろ orz) 気付いたが、 上記 TWRP のダウンロードページ に、 「Downgrade_to_4.12.40.1698」 というディレクトリがあった。 Android 6 に戻したいという (私と同様な) 人が多いのだろう。

まず ASUS の Driver & Tools ページから Marshmallow の最後のバージョン WW-4.12.40.1698 の zip アーカイブをダウンロード。これを展開する:

senri:~/ZS570KL $ unzip UL-Z016-WW-4.12.40.1698-user.zip
Archive:  UL-Z016-WW-4.12.40.1698-user.zip
signed by SignApk
 extracting: system.patch.dat        
  inflating: META-INF/com/android/metadata  
  inflating: META-INF/com/google/android/update-binary  
  inflating: META-INF/com/google/android/updater-script  
  inflating: boot.img                
  inflating: file_contexts           
  inflating: firmware-update/NON-HLOS.bin  
  inflating: firmware-update/adspso.bin  
  inflating: firmware-update/cmnlib.mbn  
  inflating: firmware-update/cmnlib64.mbn  
  inflating: firmware-update/devcfg.mbn  
  inflating: firmware-update/emmc_appsboot.mbn  
  inflating: firmware-update/hyp.mbn  
  inflating: firmware-update/keymaster.mbn  
  inflating: firmware-update/mdtp.img  
  inflating: firmware-update/pmic.elf  
  inflating: firmware-update/rpm.mbn  
  inflating: firmware-update/tz.mbn  
  inflating: firmware-update/xbl.elf  
  inflating: logs/version            
  inflating: system.new.dat          
  inflating: system.transfer.list    
  inflating: META-INF/com/android/otacert  
  inflating: META-INF/MANIFEST.MF    
  inflating: META-INF/CERT.SF        
  inflating: META-INF/CERT.RSA       
senri:~/ZS570KL $ 

zip アーカイブの中の boot.img が android のブート・イメージ。 このファイルの中に、 Linux カーネルと initramfs が含まれている。 これを boot パーティションへ書込む:

senri:~/ZS570KL $ fastboot flash boot boot.img
target reported max download size of 536870912 bytes
sending 'boot' (13305 KB)...
OKAY [  0.524s]
writing 'boot'...
OKAY [  0.021s]
finished. total time: 0.545s
senri:~/ZS570KL $ 

zip アーカイブの中の firmware-update ディレクトリ下の 13個のファイルが、 ブートローダ等のファームウェア。

拡張子が *.bin のファイル (2個) はファイルシステムのイメージ。 NON-HLOS.bin は /firmware ディレクトリに、 adspso.bin は /dsp ディレクトリに、 それぞれ読み取り専用でマウントされる。 どちらも AMSS モデム関連のディレクトリ。 NON-HLOS.bin は modem パーティションへ、 adspso.bin は dsp パーティションへ、 それぞれ書込む:

senri:~/ZS570KL $ fastboot flash modem firmware-update/NON-HLOS.bin
target reported max download size of 536870912 bytes
sending 'modem' (81916 KB)...
OKAY [  3.166s]
writing 'modem'...
OKAY [  1.895s]
finished. total time: 5.060s
senri:~/ZS570KL $ fastboot flash dsp firmware-update/adspso.bin
target reported max download size of 536870912 bytes
sending 'dsp' (16384 KB)...
OKAY [  0.652s]
writing 'dsp'...
OKAY [  0.401s]
finished. total time: 1.053s
senri:~/ZS570KL $ 

拡張子 *.mbn は multi-boot binary ファイル (8個) で、 ARM の ELF (Executable and Linkable Format) オブジェクト。 keymaster.mbn と cmnlib.mbn は 32bit の共通ライブラリで、 keymaster は証明書の暗号鍵を管理する。 cmnlib64.mbn は 64bit の共通ライブラリ。

rpm.mbn と emmc_appsboot.mbn は 32bit ARM の ELF 実行ファイル。 rpm は 「resources & power manager」 を意味し、 プライマリ・ブートローダの役割を果たす。 emmc_appsboot はセカンダリ・ブートローダで、 このブートローダが android のカーネルを起動する。

残りの devcfg.mbn, hyp.mbn, tz.mbn は 64bit ARM の ELF 実行ファイル。 機能の詳細は分からないが、 devcfg は device config、 hyp は hypervisor、 tz は trust zone のことだと思われる。

詳細不明なファイルが多いが (^^; 以上 8個の *.mbn ファイルを、 それぞれ対応するパーティションへ (emmc_appsboot.mbn は aboot パーティションへ、 それ以外はファイル名と同じ名称のパーティションへ) 書込む:

senri:~/ZS570KL $ fastboot flash cmnlib firmware-update/cmnlib.mbn
target reported max download size of 536870912 bytes
sending 'cmnlib' (200 KB)...
OKAY [  0.029s]
writing 'cmnlib'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.060s
senri:~/ZS570KL $ fastboot flash cmnlib64 firmware-update/cmnlib64.mbn
target reported max download size of 536870912 bytes
sending 'cmnlib64' (254 KB)...
OKAY [  0.030s]
writing 'cmnlib64'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.061s
senri:~/ZS570KL $ fastboot flash devcfg firmware-update/devcfg.mbn
target reported max download size of 536870912 bytes
sending 'devcfg' (39 KB)...
OKAY [  0.040s]
writing 'devcfg'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.070s
senri:~/ZS570KL $ fastboot flash aboot firmware-update/emmc_appsboot.mbn
target reported max download size of 536870912 bytes
sending 'aboot' (749 KB)...
OKAY [  0.047s]
writing 'aboot'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.077s
senri:~/ZS570KL $ fastboot flash hyp firmware-update/hyp.mbn
target reported max download size of 536870912 bytes
sending 'hyp' (257 KB)...
OKAY [  0.038s]
writing 'hyp'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.069s
senri:~/ZS570KL $ fastboot flash keymaster firmware-update/keymaster.mbn
target reported max download size of 536870912 bytes
sending 'keymaster' (220 KB)...
OKAY [  0.032s]
writing 'keymaster'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.062s
senri:~/ZS570KL $ fastboot flash rpm firmware-update/rpm.mbn
target reported max download size of 536870912 bytes
sending 'rpm' (224 KB)...
OKAY [  0.039s]
writing 'rpm'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.069s
senri:~/ZS570KL $ fastboot flash tz firmware-update/tz.mbn
target reported max download size of 536870912 bytes
sending 'tz' (1600 KB)...
OKAY [  0.084s]
writing 'tz'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.114s
senri:~/ZS570KL $ 

拡張子 *.elf は 64bit ARM の ELF 実行ファイル (2個)。 *.mbn との違いは不明 (誰か教えて!)。 pmic.elf は power management IC のこと? xbl.elf は eXtended bootloader のことだろう。 それぞれ対応する同名のパーティションへ書込む:

senri:~/ZS570KL $ fastboot flash pmic firmware-update/pmic.elf
target reported max download size of 536870912 bytes
sending 'pmic' (41 KB)...
OKAY [  0.040s]
writing 'pmic'...
(bootloader) partition_need_check.
OKAY [  0.030s]
finished. total time: 0.070s
senri:~/ZS570KL $ fastboot flash xbl firmware-update/xbl.elf
target reported max download size of 536870912 bytes
sending 'xbl' (1780 KB)...
OKAY [  0.090s]
writing 'xbl'...
(bootloader) partition_need_check.
OKAY [  0.034s]
finished. total time: 0.124s
senri:~/ZS570KL $ 

mdtp.img は Mobile Device Theft Prevention のこと? ファイルの形式すら不明だが、 mdtp パーティションへ書込む:

senri:~/ZS570KL $ fastboot flash mdtp firmware-update/mdtp.img
target reported max download size of 536870912 bytes
sending 'mdtp' (8086 KB)...
OKAY [  0.324s]
writing 'mdtp'...
OKAY [  0.218s]
finished. total time: 0.542s
senri:~/ZS570KL $ 

以上で zip アーカイブの中の firmware-update ディレクトリ下の 13個のファイルの書き込みが完了した。

そして、 zip アーカイブの中の system.new.dat と system.transfer.list が、 android を起動したときに /system にマウントされる ext4 ファイルシステムのイメージ。 つまり Android OS の本体。 3.6GB ほどあり、 zip アーカイブの中の他のファイルと比べてケタ違いに大きい。

一般にファイルシステムのイメージは、 データが無い部分が散在する 「疎なファイル」(sparse file) になる。 フラッシュメモリに書込むときに、 データが無い 「0」 ばかりのブロックを書込むのは効率が悪いので、 「0」 なブロックを飛ばしたデータのファイル system.new.dat と、 そのデータをフラッシュメモリのどこへ書込むか指定するファイル system.transfer.list に分かれている。

残念なことに fastboot コマンドは system.transfer.list を扱えないので、 あらかじめ system.new.dat と system.transfer.list から ext4 ファイルシステムのイメージを作成しておいて fastboot コマンドに与える必要がある。 sdat2img コマンドを使うと、 system.new.dat と system.transfer.list から、 (疎な) イメージ system.img を作成できる:

senri:~/ZS570KL $ sdat2img system.transfer.list system.new.dat system.img
sdat2img binary - version: 1.0

Android Marshmallow 6.0 detected!

Copying 32770 blocks into position 0...
Copying 2 blocks into position 33009...
Copying 32016 blocks into position 33519...
Copying 2 blocks into position 65536...
Copying 32257 blocks into position 66046...
Copying 2 blocks into position 98304...
Copying 2 blocks into position 98545...
Copying 32016 blocks into position 99055...
Copying 2 blocks into position 131072...
Copying 32257 blocks into position 131582...
Copying 2 blocks into position 163840...
Copying 2 blocks into position 164081...
Copying 32016 blocks into position 164591...
Copying 2 blocks into position 196608...
Copying 32257 blocks into position 197118...
Copying 2 blocks into position 229376...
Copying 2 blocks into position 229617...
Copying 32016 blocks into position 230127...
Copying 2 blocks into position 262144...
Copying 32257 blocks into position 262654...
Copying 2 blocks into position 294912...
Copying 2 blocks into position 295153...
Copying 32016 blocks into position 295663...
Copying 2 blocks into position 327680...
Copying 32257 blocks into position 328190...
Copying 2 blocks into position 360448...
Copying 32257 blocks into position 360958...
Copying 2 blocks into position 393216...
Copying 32257 blocks into position 393726...
Copying 2 blocks into position 425984...
Copying 32257 blocks into position 426494...
Copying 2 blocks into position 458752...
Copying 32257 blocks into position 459262...
Copying 2 blocks into position 491520...
Copying 32257 blocks into position 492030...
Copying 2 blocks into position 524288...
Copying 32257 blocks into position 524798...
Copying 2 blocks into position 557056...
Copying 32257 blocks into position 557566...
Copying 2 blocks into position 589824...
Copying 32257 blocks into position 590334...
Copying 2 blocks into position 622592...
Copying 32257 blocks into position 623102...
Copying 2 blocks into position 655360...
Copying 32257 blocks into position 655870...
Copying 2 blocks into position 688128...
Copying 32257 blocks into position 688638...
Copying 2 blocks into position 720896...
Copying 32257 blocks into position 721406...
Copying 2 blocks into position 753664...
Copying 32257 blocks into position 754174...
Copying 2 blocks into position 786432...
Copying 32257 blocks into position 786942...
Copying 2 blocks into position 819200...
Copying 2 blocks into position 819441...
Copying 32016 blocks into position 819951...
Copying 2 blocks into position 851968...
Copying 32250 blocks into position 852478...
Copying 2 blocks into position 884736...
Copying 2 blocks into position 884977...
Copying 32016 blocks into position 885487...
Copying 2 blocks into position 917504...
Copying 2820 blocks into position 918014...
Copying 2 blocks into position 950272...
Copying 24508 blocks into position 950782...
Copying 7689 blocks into position 975291...
Skipping command zero...
Skipping command erase...
Done! Output image: ~/ZS570KL/system.img
senri:~/ZS570KL $ 

そして、作成した (疎な) system.img を system パーティションへ書込む。 fastboot コマンドは、 与えられた system.img が疎なファイルであることを認識し、 まず system パーティション全体を erase した後、 必要なブロックのみを ZS570KL へ転送して書込んでいる:

senri:~/ZS570KL $ fastboot flash system system.img
target reported max download size of 536870912 bytes
Invalid sparse file format at header magi
erasing 'system'...
OKAY [  0.002s]
sending sparse 'system' (513215 KB)...
OKAY [ 20.270s]
writing 'system'...
OKAY [  4.815s]
sending sparse 'system' (524224 KB)...
OKAY [ 20.871s]
writing 'system'...
OKAY [  5.201s]
sending sparse 'system' (523318 KB)...
OKAY [ 20.565s]
writing 'system'...
OKAY [  4.793s]
sending sparse 'system' (486869 KB)...
OKAY [ 19.371s]
writing 'system'...
OKAY [  4.688s]
sending sparse 'system' (513436 KB)...
OKAY [ 20.188s]
writing 'system'...
OKAY [  4.710s]
sending sparse 'system' (512989 KB)...
OKAY [ 20.225s]
writing 'system'...
OKAY [  4.705s]
sending sparse 'system' (506575 KB)...
OKAY [ 20.021s]
writing 'system'...
OKAY [ 22.541s]
sending sparse 'system' (67808 KB)...
OKAY [  2.684s]
writing 'system'...
OKAY [  0.475s]
finished. total time: 196.124s
senri:~/ZS570KL $ 

「header magi」 とは何ぞ? 東方より来たりし三賢者? と思ったら、 「header magic」 と表示しようとしてるのだけど、 fastboot コマンドの内部のバッファの大きさが 1 バイト足らなくて、 1 文字欠けてしまったらしい (まあ magic の語源は magi なので当たらずとも遠からじだが)。 system/core/libsparse/sparse_read.c を見ると、

static void verbose_error(bool verbose, int err, const char *fmt, ...)
{
        char *s = "";
        char *at = "";

…… (中略) ……

                size = vsnprintf(NULL, 0, fmt, argp);

…… (中略) ……

                at = malloc(size + 1);

…… (中略) ……

                vsnprintf(at, size, fmt, argp);

…… (中略) ……

                if (err == -EINVAL) {
                        sparse_print_verbose("Invalid sparse file format%s%s\n", s, at);

…… (中略) ……

struct sparse_file *sparse_file_import(int fd, bool verbose, bool crc)
{

…… (中略) ……

        if (sparse_header.magic != SPARSE_HEADER_MAGIC) {
                verbose_error(verbose, -EINVAL, "header magic");
                return NULL;
        }

などと書いてある。 せっかくメッセージの長さを調べて、 ちゃんと 1 バイト大きい (size + 1) バッファを確保しているのに、 vsnprintf の第2引数に (size + 1 ではなく) size を与えているために、 最後の 1 文字が欠けてしまうというバグ (>_<) どうしてこんな単純なバグが放置されているのか?

system/core/libsparse/sparse_format.h によると、 「sparse file format」 というファイル形式があって、 その magic (フォーマット識別子) は 0xed26ff3a であるらしい。 それなら何故 system.new.dat と system.transfer.list というファイル形式を使っているのだろう? sparse file format にすれば 1つのファイルで済むように思われるのだが...

max download size (前述の例だと 536870912 bytes) を超えるファイルを書込もうとすると、 fastboot はそのファイルが 「sparse file format」 であることを期待して、 magic が違うと 「Invalid sparse file format at header magic」 と表示する。

この場合、 通常のファイルとして読み込み直して、 内部で 「sparse file format」 へ変換するコード (sparse_read.c) になっている。 この場合、 そのファイルが疎か否かは関係なく、 単に 1ブロックが全て同じ値かどうか調べているので、 (疎でない) 通常のファイルを与えた場合も、 必要なブロックのみを転送して書込む仕様になっていた。

以上で、 Android 6.0 Marshmallow WW-4.12.40.1698 のクリーン・インストールが完了した (4月14日)。 数日間使っているが、全く何の問題も起きていない。 3月27日に問題が発覚してから 19日間、 さんざん苦しめられていたのが嘘のようだ。 もっと早く Marshmallow へ戻せばよかった。 OS のメジャーバージョンを上げるときは、 元に戻す方法を確認してからにすべきと改めて痛感した。

More...
Filed under: Android — hiroaki_sengoku @ 11:04
2013年12月13日

nexus5 の充電をワイヤレス (Qi 給電) にしたので、バックアップもワイヤレスにしてみた 〜 ssh サーバを nexus5 上で動かす 〜 hatena_b

昨今の円安のため香港ドルが高い (>_<)。 昨年は 10円/HK$ 台だったのに、いまや 13円/HK$ を超えている。 香港 (に限らないが) の人たちが大挙して日本に買物に来る気持ちが分かる。 これだけ円安が進めば、 日本じゅうどこへ行っても、 全てのものが安く感じられるのだろう。

ARENA Scientific Icey QI Charging Pad

逆に、 日本人が香港へ行くと、全てのものが以前より 3割ほど高く見えるわけで、 物欲が萎えてしまいほとんど買物しなかった (おまけに、香港に行く直前に日本で nexus 5 を買ってしまったし)。 とはいえ、 香港までわざわざ行っておきながら何も買わないというのもアレなので、 Qi 充電器を買ってみた。 深水埗 黄金電腦商場 地下35號舖 Sunny Computer Digital Co 力生電腦數碼公司 →

HK$199 = 約2700円なので、 ちっとも安くない。 「おにぎり」 こと、 ワイヤレスチャージャー 03 なら 2200円くらいで売っているが、 「おにぎり」 の電源が専用アダプタであるのに対し、 これは汎用の micro USB ケーブルが使えるのでよしとしよう。

この Qi 充電器を USB ハブの余っているポートにつないで机の上に置き、 その上に nexus 5 を置く。 使ってみると想像以上に便利。 電話がかかってきたとき、 以前はいちいち USB ケーブルを抜いていたのだけど、 Qi 充電器ならサッと nexus 5 を手に取れるし、 電話が終わったら Qi 充電器の上に戻すだけ。 はやく全てのケータイが Qi に対応して、 喫茶店などのテーブルに Qi 充電器を標準装備して欲しいなどと思う今日このごろ。

充電がワイヤレスなのに、 PC との通信に USB ケーブルを使っていては片手落ちである。 私は普段 rsync を使って nexus 5 上のデータを丸ごと PC へバックアップしているが、 有線な adb (Android Debug Bridge) を使うのは止めて Wi-Fi を使うことにした。

ここで注意したいのは、 主導権を握る (コントロールする) のは PC 側でなければならない、 ということ。 スマホ側 (nexus 5) が主導して rsync を起動するアプリならすでにいくつか出回っているが、 PC が目の前にあるときに何が嬉しくてスマホの小さい画面をいじらなきゃならないのかと思う。 Qi 充電器の上に置いたら、 もう 1 タッチといえどスマホには触りたくない。 全ての作業は PC のキーボードで完結させたい。

つまり、 PC がクライアントとなり、 スマホをサーバとして扱いたい、 ということ。 サーバのキーボードやモニタはトラブル発生時でもなければ使わないのと同様、 家にいるときはスマホは充電器の上に置きっぱなしにしておきたい。 あるいは寝室専用になってしまっているスマホ (各部屋に一台以上、専用スマホ/タブレットが置きっぱなしにしてある) は、 寝室に置きっぱなしのままで (別の部屋の) PC から操作したい。 スマホを PC から操作できれば、 コマンド一発で、 家じゅうのスマホ (10台くらいある) をいっぺんにバックアップしたり、 相互にデータを同期させたり、 何でも思いのまま (^^)。

スマホをサーバとして扱うには、 スマホ上で ssh サーバを動かしておけばよい、 ということで早速 dropbear サーバ (軽量 ssh サーバ) と rsync を nexus 5 (だけでなく私が持っている全ての android スマホ) にインストールした。

以下、インストールのメモ:

More...
Filed under: Android,システム構築・運用 — hiroaki_sengoku @ 09:07
2013年11月23日

nexus5 に ストラップを付けてみた ~テグスを使って~ hatena_b

ほとんどの人にとって無用なケータイ ストラップ。 iPhone を最右翼として、 最近の (海外製) スマホのほとんど (全て?) にはストラップを付ける穴 (ストラップ ホール) がない。 しかしながら (私を含めて) ストラップが必要と思う人にとっては、 ストラップが付けられないケータイは使う気がしない。 私が使うストラップは、 腰に付けたまま電話できる長いタイプ。 日本ではあまり見かけないので香港でまとめ買いした。 一本約 80円。 常に腰と結び付けたままなので、 ケータイをどこかへ置き忘れるということがないし、 頭の高さからケータイを落下させても、 ストラップが最大限に伸びたとき地面に激突しないような長さのものを使っている。

同じように考える人が少なくないようで、 ストラップ ホールが無いケータイに、 なんとかストラップを付けようと模索する WWW ページが沢山見つかる。 nexus 5 においても、 早速ストラップを付けた人がいるようだ。

しかし、 nexus 5 の裏蓋と nexus 5 本体との間には隙間がほとんどなく、 このページで解説されているようにストラップの紐 (直径 0.7mm くらいある) を間に挟むのは少々無理がある。 裏蓋には NFC や Qi (チー) などのアンテナがあり、 本体と裏蓋が密着していないとアンテナとの接触が不良になる恐れがある。

そこで、 nexus 5 の機能には極力影響を与えず、 もちろん nexus 5 を傷付けずに、 ストラップを付ける方法を考えてみた。

nexus5 with strap

幸い、 nexus 5 の下部には、 スピーカーとマイク用の穴 (直径約 0.8mm) が、 左右にそれぞれ 16個づつある。 しかもスピーカー穴の内部には、 スピーカーとの間に直径 0.5mm 程度の糸なら無理無く通せる空間がある。 内部がギッシリ詰まっている nexus 5 において、 この空間は例外的な存在。 そこで、 太さ約 0.52mm の 10号テグス (ナイロン ライン) をスピーカー穴に通してみた。↓

More...
Filed under: Android — hiroaki_sengoku @ 08:51
2012年3月5日

新規一括 0円の Galaxy Nexus (SC-04D) を買ってみた 〜 国際版 Galaxy Nexus と全く同じにする hatena_b

mandatory option よほど在庫が積み上がってるのか、 ドコモ SC-04D Galaxy Nexus が新規一括 0円で売っていた。 あんなに大々的な宣伝を行なっておいて何をやってるんだか...

私が見た店は、 0円の条件として、 ひとりでも割50 が必須。 つまり 2年間の契約期間中に割引サービスを廃止 (あるいは回線ごと解約) する場合は、 9,975円の解約金が必要となる。 契約事務手数料 3000円の他、 初月のみ 「パケ・ホーダイ ダブル2定額」 など諸々のオプション (写真→) を付ける必要があり、 違約金以外に 5000〜7000円ほどかかる。 加入条件のオプションの多さに圧倒されるが、 「パケホダブル」 以外のオプションは初月無料がほとんどで、 今月中に解約すれば費用はかからない。 パケホダブルは月額2,100円だが日割が適用されるので、 月末近くに契約すればもっと安くなる (あいにく私が買ったのは月初だが)。

なんたって Galaxy Nexus である。 わずか 3ヶ月前に HK$5398 (約 54,000円) もしたケータイが、 2万円かからずに入手できるのは見逃せない。 思わず衝動買い (0円なので 「買った」 とは言えない?) してしまった。

SC-04D System Update Dialog 帰宅して SC-04D の電源を入れようとすると... う、電源が ON のままだったか。 てっきりショップで電源を OFF にして渡してくれたものと思い込んでいた。 そしてディスプレイが点灯してそこに表示されたのは、 無情な 「システムアップデート」 の文字。

あとで分かったが、 ショップを出て帰宅するまでの短い時間に、 9.3MB ものアップデートファイル (yakjusc_ITL41D_to_ICL53F.zip) が転送されていて、 この日のパケット代が、 いきなり 3800円。 SC-04D を安く入手しようという目論見が、いきなりパー。 なにもこんなタイミングでアップデートファイルを送りつけなくても... 無線LAN に接続するまで待って欲しかった。

で、 こんなアップデートのお知らせはサックリ無視して真っ先に行なったのは、 ブートローダのアンロック (fastboot oem unlock)。 いきなりドコモの保証がパー。

一度フツーに起動させてフラッシュメモリの初期化を行なった後 (一度フツーに OS を起動しないと、 フラッシュメモリにアクセスできない)、 Clockwork Mod Recovery を fastboot で起動する (fastboot boot <kernel>)。 そして、 初期状態のドコモ純正ROM を、 ベースバンド込みで丸ごとバックアップ。 これでいつでも元の状態に戻せるようになったので、 ClockworkMod Recovery をフラッシュメモリの 「recovery」 領域 (リカバリ用のカーネルを格納する領域) に書込む (fastboot flash recovery <kernel>)。

senri:~ $ fastboot oem unlock
...
OKAY [ 52.022s]
finished. total time: 52.022s
senri:~ $ fastboot boot recovery-clockwork-5.5.0.2-maguro.img
downloading 'boot.img'...
OKAY [  0.553s]
booting...
OKAY [  0.329s]
finished. total time: 0.882s
senri:~ $ adb shell
~ # dd if=/dev/block/platform/omap/omap_hsmmc.0/by-name/radio of=/sdcard/SC04DOMKKD.img bs=1024
16384+0 records in
16384+0 records out
16777216 bytes transferred in 6.601 secs (2541617 bytes/sec)
~ # exit
senri:~ $ adb pull /sdcard/SC04DOMKKD.img
4569 KB/s (16777216 bytes in 3.585s)
senri:~ $ mkdir backup
senri:~ $ adb pull /sdcard/clockworkmod/backup/ backup/
pull: building file list...
pull: /sdcard/clockworkmod/backup/2012-03-04.01.09.21/nandroid.md5 -> backup/2012-03-04.01.09.21/nandroid.md5
pull: /sdcard/clockworkmod/backup/2012-03-04.01.09.21/cache.ext4.tar -> backup/2012-03-04.01.09.21/cache.ext4.tar
pull: /sdcard/clockworkmod/backup/2012-03-04.01.09.21/data.ext4.tar -> backup/2012-03-04.01.09.21/data.ext4.tar
pull: /sdcard/clockworkmod/backup/2012-03-04.01.09.21/system.ext4.tar -> backup/2012-03-04.01.09.21/system.ext4.tar
pull: /sdcard/clockworkmod/backup/2012-03-04.01.09.21/recovery.img -> backup/2012-03-04.01.09.21/recovery.img
pull: /sdcard/clockworkmod/backup/2012-03-04.01.09.21/boot.img -> backup/2012-03-04.01.09.21/boot.img
6 files pulled. 0 files skipped.
4308 KB/s (384848109 bytes in 87.229s)
senri:~ $ fastboot flash recovery recovery-clockwork-5.5.0.2-maguro.img
sending 'recovery' (5300 KB)...
OKAY [  0.552s]
writing 'recovery'...
OKAY [  1.015s]
finished. total time: 1.567s

再び通常起動して、 (今度は抜かり無く) 無線LAN の設定を行なってから、放置 (念のため、 「データ通信を有効にする」 を無効にしておく)。 ほどなく再びアップデートファイルがダウンロードされてきた。

フラッシュに書込んだ ClockworkMod Recovery を起動して、 /cache に格納されていた 94ec403ff247.update_yakjusc_ITL41D_to_ICL53F.zip (アップデートファイル) を /sdcard へコピー。 で、ClockworkMod Recovery からこの zip ファイルをインストール。 この時点で (ClockworkMod Recovery を書込んだ) recovery 領域は、 ドコモ (というか Google) 純正リカバリ用カーネルで上書きされる。

これでビルド番号が ICL53F.SC04DOMLA1 になった。 ちなみに末尾の 「LA1」 というのは、 「L」 が 2012年 (L は 12番目のアルファベットだから) を意味し、 その次の 「A」 が 1月 (A は 1番目のアルファベット) を意味し、 その次の 「1」 は、 その月にリリースされた最初のバージョンであることを意味する。

「L53F」 も同様に、 「L」 が 2011年第4四半期 (2009年第1四半期を A として) を意味し、 「53」 が、 その四半期で 53日目であることを意味し、 「F」 は同じ日の 6番目のリリースであることを意味するらしい。 先頭の 「I」 は、 Ice Cream Sandwich の頭文字。

このバージョンのドコモ純正ROM も、 上記と同様に ClockworkMod Recovery を使って、 ベースバンド込みで丸ごとバックアップ。 無線LAN の設定を行なったので厳密には初期状態ではないが、 初期化するのが面倒なので、 まあよしとする。

以上で、 いつでもドコモ純正ROM に戻る手段を確保できたので、 ここでドコモ純正ROM にサヨナラする。 私が普段使ってる Galaxy Nexus (3ヶ月前に香港で買った) を丸ごとバックアップし、 そのバックアップファイルを SC-04D へ、 ClockworkMod Recovery を使って丸ごとリストア (もちろん、リストアする代わりに Google 純正ROM を書き込んで、 国際版の初期状態にしてもよい)。

Galaxy Nexus * 2 左が香港で買った Galaxy Nexus i9250 GSM (もちろん SIM ロックフリー),
右が今回買ったドコモ SC-04D

裏ブタ記載の文字が異なる (なぜ SC-04D には Google ロゴがない?) 他は、 全く同じ外観。 丸ごとリストアしたので、 フラッシュメモリの中身も全く同じ (もちろん IMEI など、端末固有のデータは異なる)。

丸ごとリストアしたことによって OS も書き換えてしまったので、 ドコモのテザリングの制約 (勝手に APN が dcmtrg.ne.jp に切り替わるとか) から解放される (はず)。 「パソコンなどの外部機器を接続した通信」 ではなく スマートフォン定額通信の範囲でテザリングできるはずだが、 私は興味がない (ドコモは今月一杯で解約するつもりだし) ので試していない。 OS が書き変わっても、 IMEI 上は 「ドコモ純正端末」 なので、 フツーに spモードを利用できる。 こうなってくると、 ドコモが何のために接続する端末の IMEI を制限しているのか分からなくなる。

二つの Galaxy Nexus の OS は同じになったが、 SC-04D にはまだ SIM ロックがかかっている。 ドコモショップで SIMロック解除の手続きを行なってくれるが、 ブートローダをアンロックした Nexus も対象となるのか? ブートローダだけならまだしも、 OS ごと入れ替えてしまっていても拒否されないのか?

SIMロックは、 (Samsung製) Android 端末の場合 /data/radio/nv_data.bin などで設定されている。 このファイルを 「正しく」 書き換えることができれば SIMロックを解除できるのだが、 このファイルが改変されていないか検証するために、 /data/radio/nv_data.bin.md5 に MD5 値が保存されている。 そして残念なことに、 この MD5 の算出方法が不明。 もちろん、 単純に nv_data.bin の MD5 を算出するだけではダメで、 何らかの秘密の seed が必要なのだと思われる (ファイル名の通り MD5 が使われていると仮定してだが)。

MD5 の値が一致しなかったなどの理由で検証にパスしなかった場合、 あるいは (Android を初期化したなどの理由で) そもそも /data/radio/nv_data.bin が存在しなかった場合は、 /factory/nv_data.bin を使って /data/radio/nv_data.bin が作られる。 ところが、 /factory/nv_data.bin にも、 /factory/nv_data.bin.md5 があって、 その内容が書き換えられないように保護されている。

/factory/nv_data.bin が検証をパスしないと、 /factory/.nv_data.bak が使われるらしい。 何段もの防御があって、 なかなか突破するのが難しそうである。 そこで、 改変した nv_data.bin を /factory/nv_data.bin と /factory/.nv_data.bak にコピーして、 検証が通らなくて書き戻そうにも、 本来の nv_data.bin を参照できないようにしてみた。

すると... 驚いたことに、 ドコモ以外の SIM でも使えてしまった。 google で検索すると、 あちこちで SIMロックの解除方法を説明したページが見つかるが、 ほとんど (全て?) は同様の方法であるようだ。 しかし、 この方法には重大な欠点がある。 それは、 IMEI の値が 「004999010640000」 になってしまう点。 キャリア側で IMEI の値をチェックしていなければ、 そのまま使えてしまうのかもしれないが、 これでは 「ここに不正改造無線局があります〜」 と吹聴して歩き回るのと大差なく、 甚だ具合がよろしくない。

SIM Unlock PIN というわけで万策尽きたので、 解除コード (Unlock Code) を業者から買うことにした。 だいたい $30 前後で、 ドコモ (3,150円) より若干安い。 この期に及んでドコモのサービスを使わないのは、 前述した懸念点があるのと、 ドコモショップでは 「解除PIN」 を教えてもらえなかった、 という話をネット上で見かけたため。

ドコモショップの店長が、 「このコードはドコモの内部情報になるため、お客様にはお伝えできません」 と言ったらしいが、 この話が本当なら、 とんでもない話だと思う。 3,150円も取っておきながら PIN 一つ教えないとはどーいうことだ。

ドコモショップで SIM ロック解除したケータイを海外に持って行き、 現地の SIM を入れて電源を入れたら、 「SIMネットワークのロック解除PIN」 を入力する画面になってしまい往生した、 という怨嗟の声をネット上で見かけるが、 おそらく何らかの理由で /data/radio/nv_data.bin の内容が壊れてしまい、 /factory/nv_data.bin の内容で書き戻されたために、 ロック解除PIN の入力が必要になってしまったものと思われる。

つまり、 ロック解除PIN を入力して SIM ロックを解除した後、 /data/radio/nv_data.bin および /data/radio/nv_data.bin.md5 を /factory へコピーしておけばこのような事態に陥ることを回避できる。 ただし、 Android の root 権限が必要。 root 権限がないと、 そもそも /data/radio/nv_data.bin を読むことができない。 だから、 何かあったときのため、 ロック解除PIN を記録しておくことが重要。

Cell Unlocker .net で、 device 名として 「Galaxy Nexus」、 SIM ロック先のキャリアとして、 「All Asia Countries」 を指定し、 IMEI (「*#06#」 をダイヤルすると表示される) を入力した。 すると PayPal の支払い画面に遷移するので、 $29.99 の支払いボタンを押すだけ。

7時間半後、 ロック解除PIN が書かれたメールが送られてきた。 メールには、

Video proof will be required if code does not work, it's a good idea to film inputting the unlock code so we can help trouble shoot what the problem is.

と書かれていたので、 まず、 デジカメ (ビデオカメラは持ってない) を三脚にセットして動画撮影する準備を整えた。 SingTel の SIM (ドコモ以外ならどこでも) を SC-04D に入れて電源を入れる。 すると、 ↑ の写真にあるような 「SIMネットワークのロック解除PIN」 を入力する画面になったので、 デジカメで動画撮影しつつ、 メールに書かれていた unlock code を入力した。 すると、 「ネットワークのロック解除をリクエスト中...」 と表示され、 すぐ 「ネットワークロックを解除しました。」 と表示された。 数秒後、 Android のホーム画面が表示された。

以上で、 二つの Galaxy Nexus は (IMEI 以外は) 完全に同じになった。 今まで Galaxy Nexus で使っていた Softbank SIM を、 SC-04D に入れて、 今後は SC-04D のほうを使ってみようと思う。 普通に使っている限り、 たぶん、何の違いも見いだせないに違いない。

More...
Filed under: Android,SIM — hiroaki_sengoku @ 20:21
2012年2月1日

adb (Android Debug Bridge) 経由で rsync を使ってバックアップできるようにしてみた 〜 shell の echo back を回避する方法 tweets

最初の Android 4.0 端末である Galaxy Nexus は、 今までの Android 端末と異なり USB Storage として使うことができない。 PC とデータをやりとりしたいときは MTP (Media Transfer Protocol) を使えということらしい。 従来の Android 端末は、 PC と USB ケーブルで接続することによって、 Android 端末のストレージ (microSD カードなど) を USB Storage として PC からアクセスすることができた。

ただし、 PC と Android 双方から同じストレージを同時に読み書きすると破綻するので、 PC からアクセスするときは、 Android 端末からそのストレージを一時的に切り離す (umount する) 実装になっている。 このため、 ストレージに Android が必要とするデータがあると、 切り離したときに問題が起きる。 例えば着信音 (着メロ) を microSD カード上に置いていると、 切り離しているときに電話がかかってきても、 その着信音を鳴せない (Nexus S などだと、 設定した着信音がアクセスできない場合は、 デフォルトの着信音に切り替わる)。

MTP はブロックデバイス単位でなくファイル単位でストレージを管理する。 したがって PC との接続時でも Android 端末からストレージ全体がアクセスできなくなるという問題がない。 しかも MTP は Windows Vista 以降なら標準でサポートしている。 と、 このように (Samsung と Google は) 考えて、 Galaxy Nexus では USB Storage の代わりに MTP を使うようにしたのだろう (Samsung の一つ前の世代の Android 端末である Galaxy S でも MTP が使える)。 なお、 今後登場する Android 4.0 (Ice Cream Sandwich, ICS) 端末で USB Storage 機能が廃止されるかどうかは不明。 少なくとも Nexus S を ICS にシステムアップデートしても、 従来通り USB Storage を使うことができた。

一見妥当のように見える MTP の採用だが、 実際に使ってみると問題が多い。 ストレートに言えば 「全く使い物にならない」。 Windows XP だと Windows Media Player 10 以降のバージョンをインストールしないと MTP が使えないし、 Windows 以外だと、 標準で MTP をサポートしている OS はほとんどない。 しかも Windows でも、 MTP うんぬん以前に Samsung 端末用の USB ドライバを入手するのが大変。 以前は SAMSUNG_USB_Driver_for_Mobile_Phones.exe が単体で配布されていたようだが、 今は Kies をインストールしないと入手できない (-_-メ)。

さんざん苦労して MTP が使えるようにしても、 データ転送速度が極めて遅い (まだ実装がこなれてないため?)。 さらに、 普通のブロックデバイス (あるいはファイルシステム) として OS から見えないため、 MTP 対応を謳っていない限り、 普通のバックアップアップツールでは扱えないことが多い。 数MB 程度のファイルを一つ二つコピーする程度であればさほど問題無いが、 バックアップなど大量のデータを転送したい場合は不向き。

私の場合、 Galaxy Nexus のストレージ (/mnt/sdcard パーティション, 実際には /data/media を FUSE で /mnt/sdcard に mount している) を毎日 PC (Linux マシン) へバックアップしたい。 全部で数GB の大きさがあるので、 全データを毎回コピーするのは非現実的。 そこで rsync backup for Android アプリを使ってみた。 このアプリは、 内部に rsync と ssh コマンドを持っていて、 rsync を以下のような形で実行している:

/data/data/eu.kowalczuk.rsync4android/files/rsync -vHrltD \
    --chmod=Du+rwx,go-rwx,Fu+rw,go-rw --no-perms --delete-after \
    -e '/data/data/eu.kowalczuk.rsync4android/files/ssh -y -p 22 \
            -i /data/data/eu.kowalczuk.rsync4android/files/dss_key' \
    /mnt/sdcard/ sengoku@senri.gcd.org:~/android/sdcard/

Galaxy Nexus には有線LAN が無いので WiFi 経由で通信することになるが、 PC 同士で rsync する場合と全く同じで、とても簡単。 Android も Linux なのだから、 MTP みたいな得体の知れないものを無理に使うより、 使い慣れた rsync の方が断然 (・∀・)イイ!!

とはいえ、 使ってると WiFi の通信の遅さが気になってきた。 「USBテザリング」 を使えば USB ケーブル経由で TCP/IP 通信を行なうこともできるが、 rsync でデータ転送を行なうためだけにテザリングするのは牛刀な感じが否めない。 また、 テザリングの場合 Android の default route が 3G 回線へ向いてしまうので、 特定のアドレス (プライベート IP アドレス) を USB へ向ける route 設定が必要。

そもそも Android 端末と PC とは adb (Android Debug Bridge) でデータをやりとりしているのだから、 rsync も adb 上で行ないたいと思うのが人情というもの。

前フリが長くなったが、ここからが本題。

More...
Filed under: Android — hiroaki_sengoku @ 09:44
2012年1月4日

スマートフォン用の外付バッテリー tweets

あけましておめでとうございます。今年もよろしくお願いします。

近頃のスマートフォン (スマホ) は、 ちょっと使ってるとバッテリーが一日もたない。 出先でバッテリー切れになると大変困るので、 外付バッテリーを携帯している人も多いのではないか。 外付バッテリーには、 リチウムイオン二次電池 (充電式電池, 充電池) を利用するものと、 単三乾電池 (あるいはエネループ等のニッケル水素二次電池) を利用するものがあるが、 前者は (安全性の観点から) 充電池を交換できないことが多く、 充電池が寿命を迎えると使い捨てになってしまう (eneloop mobile booster KBC-L54D など。3900円もするものを使い捨てにするのはモッタイナイ)。

一方後者は、 リチウムイオン充電池に比べると質量あたりの蓄えられる電力量 (重量エネルギー密度) が小さい。 18650 リチウムイオン充電池が、 3.7V * 2400mAh / 47g = 188Wh/kg 程度あるのに対し、 単三のエネループは 1.2V * 1900mAh / 27g = 84Wh/kg と、 リチウムイオン充電池の半分以下しかなく、 スマホの充電用としては力不足。

特に、 単三形ニッケル水素充電池 2本を用いる外付バッテリー (eneloop stick booster など) は、 2.4V (1.2V * 2) を倍以上の 5V (USB の電源電圧) に昇圧して出力するため、 900mAh 程度 (1.2V * 2 * 1900mAh / 5V, 損失があるはずなので実際にはもっと少ないはず) しか出力できず、 スマホを充電しても満充電にはほど遠い。 単三形ニッケル水素充電池を用いるなら 3本〜4本を直列すべきだが、 そうすると重く大きくなってしまい常時携帯するには不適。

というわけで、 私はスマホの充電に、 いわゆる中華充電器 (18650 Batttery Powered USB Bright Torch, 本体側面に 「PORTABLE POWER」 と書かれている) を使っている。 これはリチウムイオン充電池を利用する外付バッテリーだが、 18650 と呼ばれる (直径 18mm 長さ 65.0mm という意味) 安価な汎用リチウムイオン充電池 (一個 300円〜500円) を用いるため、 充電池が寿命を迎えたら充電池のみ交換できる。 18650 充電池の電圧 3.7V を 5V に昇圧して USB コネクタで出力する他、 充電回路も組み込まれているので、 USB ケーブルをつなぐだけで充電も可能。

18650 battery

写真 ↑ の直方体 (白と黒) が中華充電器。 黒い方は裏蓋を外して中身の 18650 充電池が見える状態にして撮影。 手前の面の USB A ソケットに USB ケーブルを接続して充放電を行なう。 中の赤い 18650 充電池は、 ノートPC 用のバッテリーを殻割りして取り出した充電池なので、 接着剤を剥した痕がある。 その隣の青い (「UltraFire」 と書いてある) 18650 充電池は、 香港の鴨寮街 (深水埗駅の出口すぐ) で買った 18650 充電池 2個パック (2個で HK$68 = 約 680円)。

Battery Graph while charging

もちろん、 安全回路なしのリチウムイオン充電池をセル単体 (つまり 18650) で扱うことは危険であり、 発火や爆発の可能性も考慮にいれた運用が求められる。 その危険性故に、 日本では 18650 は、 一般には市販されていない (秋葉原などでは売っている店もある)。 18650 を充電器から日常的に出し入れするなど、 ニッケル水素充電池 (エネループ等) と同様の感覚で扱うことは絶対に避けるべき。

この中華充電器は軽量コンパクトでありながら、 スマホを (ほぼ) 満充電にできるくらいの電力量があるので、 私は常に持ち歩いている。

Galaxy Nexus (香港で購入した GT-I9250) のバッテリーが残り 10% になった段階 (左のグラフで 19:47 の時点) で、 中華充電器による充電を開始してみた (ディスプレイを消灯したまま放置)。 2時間55分後 (22:42) に充電が停止し、 94% まで充電することができた。

Galaxy Nexus のバッテリーは、 1750mAh のリチウムイオン充電池 (スマホの中では容量が大きい方)。 3.7V 2400mAh の 18650 を 5V に昇圧して出力する中華充電器で、 Galaxy Nexus を 10% から 94% まで充電できれば悪くない。 つまり、 充電電気量は 3.7V * 2400mAh / 5V ≒ 1700mAh 程度になる (実際には損失がある) ので、 84% (94% - 10%) を充電できれば充分。 3時間弱の充電中も Galaxy Nexus が 150mAh くらいは消費しているはず (Galaxy Nexus など大抵のスマホは、 ほとんど使わない待受け状態で放置していても 12時間くらいしかバッテリーが持たない) なので、 実際に充電された電気量は 1750mAh * 84% + 150mAh = 1620mAh くらいになっていると思われる。

充電 (中華充電器にとっては放電) が停止したときの中華充電器の 18650 の電圧が 3.38V で、 リチウムイオン充電池の放電終止電圧 2.7V よりだいぶ高いが、 中華充電器の昇圧回路が機能する電圧がこのあたりなのだろう。 また、 中華充電器を満充電したときの 18650 の電圧は、 4.13V だった。 充電中の電圧は 4.2V を超えていないようなので (オシロスコープを持ってないので正確なところは不明)、 まあ大丈夫なのだろう。

Filed under: Android,香港 — hiroaki_sengoku @ 11:56
2011年8月23日

Android 端末で Picasa を使用して画像を共有できなかったので、対策を考えてみた tweets

share - Picasa

Android 上のアプリの多くは 「共有」 機能を用いて他のアプリへデータを送ることができる。 例えば画像ビューアだと、 「メニュー」 から 「共有」 を選び、 「Picasa」 を選ぶことで Android 標準の com.google.android.apps.uploader (マイアップロード) アプリを使って、 Picasa ウェブアルバムへ画像をアップロードできる (← 左図)。

ところが、 この Picasa には Google アカウントとの連係に問題があって、 Android 端末の状態によっては Picasa へのアップロードができなくなってしまう。 つまり、 「Picasa」 を選んだときに、 「Googleアカウントを追加」 する画面に遷移してしまい先に進めない (↓ 下図)。

add Google Account

既にこの携帯に登録済の Googleアカウントで、 画像をアップロードしたいのに、 「マイアップロード」 が現在の登録済アカウントを認識できず、 アカウントの追加を求めてくる。

[戻る] ボタンを押すと 「マイアップロード」 が終了してしまうので、 仕方なく [次へ] をタップして Googleアカウントを追加しようとすると、 「アカウントが既に存在します」 と言われてしまう (↓ 下図)。

Google Account exists already

「アカウントが存在する」 のが分かってるなら、 そのアカウントで画像をアップロードしろよ! と言いたくなるが、 画面には [戻る] しかないのでどうにもならない。

Google ヘルプを見ると、 http://picasaweb.google.com に一度もアクセスしない状態でアップロードを試みると、 このような二進も三進も行かない状態に陥る、 ということらしい。 そこまで分かってるなら対策しろよ、 と言いたくなる。 まあ、 Google 的には、 Picasa へ直接アップロードするのではなく、 Google+ 経由で使って欲しいということなのかもしれないが。

delete Google Account

もちろん、 現在の Googleアカウントをいったん削除すれば、 アカウントを追加することは可能になる (はず)。 しかし、 アカウントを削除しようとすると、 「携帯のメール、連絡先などのすべてのデータも削除されます」 などと脅される (右図 →)。

アカウントを削除しても、 再度アカウントを追加すれば、 削除されたメールや連絡先も再同期されるのだとは思うが、 「1つ目のアカウントを絶対削除したらだめ」 という先達の意見をスルーするのも気が引ける。

また、 知らぬ間にPicasaとの同期が始まるケースもあるらしいが、 ただ待ってるというのも能がないし、 待っていれば必ず始まるというものでもないだろう。

そこで、 アカウントを削除すること無く、 他への影響を最小限に抑えつつ、 Picasa Web Albums を同期させる方法を考えてみた。

More...
Filed under: Android — hiroaki_sengoku @ 07:31
2011年6月2日

Android 端末上で透過型プロキシを動かしてみる 〜 VPN のように使えて、しかも省電力 hatena_b

透過型プロキシ (Transparent Proxy) というのは、 ブラウザから 「見えない」 プロキシのこと。 ブラウザ自身は WWW サーバにアクセスしているつもりなのに、 ブラウザが送信したリクエストをプロキシが横取りし、 プロキシから出し直す。 サーバからのレスポンスは当然プロキシに返り、 プロキシがそれをブラウザに送信するのだけど、 パケットがブラウザに届くまでの間に送信元アドレスが書き換えられて、 サーバから直接レスポンスが届いたようにブラウザからは見える。

フツーの 「見える」 プロキシは、 ブラウザ等でプロキシ設定が必要であるのに対し、 透過型プロキシだと設定が不要。 だから一部の ISP (インターネット接続プロバイダ) などで、 フツーのプロキシの代りに使われていたりする (ユーザにプロキシ設定の方法を説明する必要がなくてサポートコストが削減できる)。 あるいは企業等で、 従業員が仕事と関係ない Web ページを閲覧していないか監視するために、 社内から社外へ接続するゲートウェイ等に透過型プロキシを設置して、 社外への http アクセスを記録していたりする (監視だけならパケットをダンプするだけでも用が足りるが、 アクセス先のサイト毎に細かな制御を行なおうとすると、 プロキシを使った方が楽)。

このように、 透過型プロキシはその存在を隠すために使われることが多いが、 ブラウザにプロキシ設定の機能が無い場合は、 透過型プロキシを使わざるを得ない。 例えば、 Android 端末で使われるブラウザの多くが、 なぜかプロキシ設定の機能を持っていない。 プロキシ経由でアクセスされると、 モバイル端末からのアクセスなのか、 フツーの PC からのアクセスなのか、 区別がつかなくなってしまうので、 あえてプロキシ設定できないようにしているのかも知れないが、 プロキシを使わないとアクセスできない場合は困ってしまう。

私の場合、 勤務先の LAN 内のサーバに社外からアクセスするとき、 社内アクセス専用のプロキシ (ここでは仮にホスト名を proxy.klab.org とする) を利用している。 例えば senri.gcd.org (自宅のサーバ、つまり社外) からこのプロキシをアクセスすると、 こんな感じ:

senri:~ $ openssl s_client -connect proxy.klab.org:443 -cert cert.pem -key key.pem -CApath /usr/ssl/certs -quiet
Enter pass phrase for key.pem:xxxxxxxx

depth=1 /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
verify return:1
depth=0 /serialNumber=-cn4oMJtlqoqfQZaTat68U68dNVbM8iQ/C=JP/O=*.klab.org/OU=GT41256819/OU=See www.rapidssl.com/resources/cps (c)09/OU=Domain Control Validated - RapidSSL(R)/CN=*.klab.org
verify return:1
CONNECT irc.klab.org:6667 HTTP/1.1

HTTP/1.0 200 OK

:irc.klab.org NOTICE AUTH :*** Looking up your hostname...
:irc.klab.org NOTICE AUTH :*** Found your hostname

行末に 「」 がある行が入力行。 「」 は Enter キー押下。 秘密鍵 key.pem でクライアント認証を受けて proxy.klab.org に接続し、 CONNECT リクエストを送信することにより、 社内の任意のサーバ:ポートに接続できる。 上記の例では社内 IRC サーバである irc.klab.org:6667 に接続しているが、 もちろん社内の任意の WWW サーバにも接続できる。

説明を簡単にするため proxy.klab.org と書いたが、 実際には VPN Warp を使っている (だから proxy.klab.org というサーバは存在しない)。 VPN Warp も stone で接続することができるので、 以下の説明は VPN Warp の場合もほとんど同様に適用することができる。

プロキシというと、 ファイアウォールの内側から外部のインターネットをアクセスするために用いるものと思っている人が多いせいか、 外からファイアウォールの内側をアクセスするためのプロキシは、 特にリバースプロキシ (reverse proxy) と呼ばれることもある。

proxy.klab.org を透過型プロキシとして利用できれば、 社内の任意のサーバに透過的にアクセスできるようになる。 例えばこんな感じ:

senri:~ $ adb shell
$ getprop ro.build.description
soju-user 2.3.4 GRJ22 121341 release-keys
$ su
# busybox traceroute irc.klab.org
traceroute to irc.klab.org (10.10.0.18), 30 hops max, 38 byte packets
 1  110.158.20.29 (110.158.20.29)  701.203 ms  90.517 ms  85.570 ms
 2  *  *  *
 3  *  *  *
 4  110.158.18.138 (110.158.18.138)  79.540 ms !A  *  *
 5  *  *  *
 6  *  ^C
senri:~ $ adb shell
$ telnet irc.klab.org 6667
:irc.klab.org NOTICE AUTH :*** Looking up your hostname...
:irc.klab.org NOTICE AUTH :*** Found your hostname

soju-user 2.3.4 GRJ22」 つまり Android 2.3.4 な Nexus S で、 パケットが届かないはずの irc.klab.org (10.10.0.18) に対して、 telnet でアクセスできている。 telnet でアクセスできるということは、 もちろん任意の IRC アプリで irc.klab.org にアクセスできるということ。

VPN (Virtual Private Network) でも同じことができる! という声が聞こえてきそうだが、 VPN だと TCP でデータを送受信していないときも、 VPN セッションを張っているだけでいろんなパケット (例えば ARP) が行き交って、 そのたびに電波が飛んで電池を消耗するので、 あまりモバイル向きではないと思う。

あるいは、 ConnectBot などを使って ssh で port forward を行なう方法もあるが、 これまた ssh セッションを張りっぱなしだと電池消耗が心配だし、 かといって IRC を使う前に毎回 ssh 接続を行なうのはメンドクサイ。 また、 通信先/宛先ポートが増えるたび port foward 設定を追加するのもメンドクサイ。

その点、 透過型プロキシだと実際にパケットが飛ぶときのみ通信が行なわれるので、 電池消耗をあまり心配せずに TCP セッションを張りっぱなしにできる。 また、 LAN 内の通信先/宛先ポートが増えても設定変更は不要。 モバイルで LAN 内にアクセスする方法として最適だと思う (便利なのに普及していないのはナゼ?)。

More...
Filed under: Android,stone 開発日記 — hiroaki_sengoku @ 09:41
2011年5月18日

nook color を買って CyanogenMod 7 をインストールしてみた 〜 レンガ化リスクが無い PC のようなタブレット端末 tweets

ハワイ滞在中、 泊ったホテルのすぐ近くに米国最大の書店チェーン Barnes & Noble があった。 当然しばしば立ち寄ったが、 電子ブックリーダ nook のデモ機を店内の一番目立つ場所 (入口を入ってすぐのところ) に並べて説明員が張り付いていて、 いつ訪れても、 来店する人々に nook を勧めたり説明したりしていた。 昨年 (2010年4月) 訪れたときもこういう状態だったので、 もうかれこれ一年以上やっているのだと思うが、 今回は昨年と違って、 nook color がラインアップに加わっている。

電子ブックリーダというと、 初代 nook や Kindle や BORDERS (米国二番手の書店チェーン, 経営再建中) の kobo eReader など、 電子インク (E Ink) をディスプレイに用いた機種が思い浮かぶが、 nook color は普通の液晶ディスプレイ。 電子書籍を読むなら圧倒的に電子インクの方が適していると思うが、 あいにく日本では電子書籍の普及は今一つだし、 近い将来普及が進むかというとなかなか難しそう (そもそも普及に懸ける書店の意気込みがぜんぜん違う)。 だから (英語がニガテな私は) 電子インクなブックリーダにはあまり興味がなかった (自炊は大変そう)。 でも、 nook color は液晶ディスプレイなので Android タブレットとして使えそう。

店頭デモ機をいじってみると、 そのままでも WWW ブラウザで日本語表示できるし、 ちょっと検索してみるとハックもいろいろ進んでいる (Android 3.0 honeycomb も動く) ようだ。 しかも $249 と Android タブレットとしてみると格安。 同じく 7インチ液晶の Galaxy Tab (382g) よりは重いが、 9.7インチ液晶の iPad2 (601g, ずっしり重くて私には無理) よりは軽い 450g.

というわけでハワイ滞在最終日に衝動買い (^^;)。 ちょうど円高が進んでいる昨今でもあるし〜 (言い訳)。 説明員のお姉さんに、 コレちょうだいと言うと、 すぐカウンターの下から (そんなところに在庫を入れていたのか) 取り出してレジへ。 ハワイ州の Sales Tax 4.710% が加算されて $260.73 (約 21000円)。 そのままスーツケースに放り込んで帰国。

で、 まず root 化。 Auto-Nooter 3.0.0 を使うと、 root 権限の取得から各種ソフトウェアのインストールまで、 全自動でやってくれるらしい。 Auto-Nooter をダウンロードして、 micro SD カードに書込み、 nook color に挿入して USB ケーブルで PC とつなぐと、 勝手にブートして勝手に全部やってくれる。 なんて簡単な...

ところが!

More...
Filed under: Android,Hawaii — hiroaki_sengoku @ 07:43
2011年5月7日

米国 T-Mobile のプリペイド SIM カードで Nexus S を使ってみた hatena_b

米国 AT&T のプリペイド SIM カードは、 $100 ぶんの度数を購入 (refill) すると、 1年間有効。 昨年訪米したときに $100 refill しておいた (さらに直前に Data Package 100MB を購入しておいた) ので今回の訪米では、 最初から (飛行機の外へ出た直後から) Nexus One で通信できた。

ところが今回は Nexus One の他に Nexus S も (ついでに言うと Galaxy S も) 持ってきている。 Nexus S は、 UMTS (W-CDMA) I/IV/VIII (2100MHz/AWS/900MHz) のみ対応で、 AT&T の UMTS II (1900MHz) は対応していない。 もちろん EDGE (Enhanced Data Rates for GSM Evolution) なら Nexus S でも対応しているが、 3G 対応スマートフォンで EDGE を使うのは悲しすぎる。 そこで AWS (Advanced Wireless Services, 上り1.7GHz 下り2.1GHz) を使う T-Mobile のプリペイド SIM カードを購入してみた。

いたるところにある AT&T ストア (ホノルル市で 7ヶ所) と比べると、 T-Mobile ストアはホノルル市に 2ヶ所しかないが、 うち一軒は幸いダウンタウンの比較的アクセスしやすい場所 1100 Alakea St にあるので、 T-Mobile store ハワイ唯一の公共交通機関 TheBus で行ってみた:

ちなみにホノルル市のもう一軒は Kahala にあるようだが、 (車無しでは) とてもアクセスしにくそう。 しかも Google Map で見ると、 「存在しないと報告されました」 などと書いてある。

ダウンタウンの T-Mobile ストアに入るなり、 スタッフが寄ってきたので Prepaid SIM を 「Pay As You Go」 プランで買いたい旨を伝えた。 ID (写真入りの身分証明書) を求められたので、 写真入りの Debit カードを提示したら、 住所を言う必要もなく、 携帯電話を見せる必要もなく、 すんなり購入できて、 しかも (頼んでないのに) Activation まで行なってくれた。

店内に椅子がなかったので、 店の外に出て石段 (上の写真の 「1100 ALAKEA」 と書いてあるブロック) に腰掛けて、 購入した SIM を Nexus S に入れてみた。 Nexus S から、 Nexus One の電話番号へかけたら、 無事 Nexus One の着信音が鳴った。 Activation はホテルに戻ってからゆっくりやろうと思っていただけに、 あっさり Nexus S が使用可能状態になってしまって拍子抜け。

More...
Filed under: Android,Hawaii,SIM — hiroaki_sengoku @ 17:57
Older Posts »