本記事は、株式会社ピー・アール・オーアドベントカレンダーの12日目です。
やりたいこと
前回、前々回で、Garmin Connectからfit形式の自分の計測データをダウンロードすることができましたが、これではまだランサムウェアに負けないための予防的措置でしかありません。
本来やりたいことは、fitデータから必要なデータを抽出し、好きなように分析してみたいという事なので、ここらでfitデータに立ち向かう必要があります。
今回も超地味かつ共感を得にくい内容で攻めていきます。
VSCodeのHex Editor
さて、対象がバイナリファイルなので当然バイナリエディタが必要になります。
バイナリエディタというと、なかなか「これ」というものが無くて、viのバイナリモードが結局使いやすかったりしてたイメージなんですが、2020年7月にVSCodeの拡張としてMicrosoftが出したHex Editorというものが結構良さそうだったので、使ってみることにしました。
Hex Editorを入れた状態でバイナリファイルをVSCodeで開こうとすると
こんなポップアップが出て、バイナリモードで開くかどうか選べるようになります。
解析してみます
参考資料
Garmin公式です。図解もあり読み解いていくためには必須のドキュメントですね。
※図は、本記事には著作権的にどうなのかわからんかったので転載していません。
File header
まず始めはFile headerで、これは以下仕様となっているそうな。
Byte | Parameter | Size(Bytes) | Description |
---|---|---|---|
0 | Header Size | 1 | ヘッダサイズを示す。最少は12byteで、この場合は12番目と13番目のCRCを含まないとのこと |
1 | Protocol Version | 1 | Protocolバージョン |
2 | Profile Version LSB | 2 | Profileバージョン |
3 | Profile Version MSB | ||
4 | Data Size LSB | 4 | Data Recordsセクションのサイズ(bytes)を示す(ヘッダ及びCRCは含まず) |
5 | Data Size | ||
6 | Data Size | ||
7 | Data Size MSB | ||
8 | Data Type Byte[0] | 4 | ここはテキストファイルで開かれたときに何のファイルかがわかるように、".FIT"というテキスト値が入る |
9 | Data Type Byte[1] | 同上 | |
10 | Data Type Byte[2] | 同上 | |
11 | Data Type Byte[3] | 同上 | |
12 | CRC LSB | 2 | |
13 | CRC NSB |
ファイルヘッダの中で重要そうなのは、Header SizeとData Sizeっぽいですね。
実際のファイルを見てみます。
先頭byteによるとヘッダは14byteのようです。上記の表でいうとCRCを含むということですね。
次にData Sizeです。ここは4byteなので4*8(bit)=int32の値を参照しましょう。16703bytesですね。
Data Records
15byte目からがData Recordsということですが、Record Headerは冒頭1byteがRecord Headerというものらしいです。
Record Header
Record Headerには2種類あり、最上位ビットで見分けるようです。
Normal Header
最上位ビット=0で、以下の仕様をとる
bit | value | Description |
---|---|---|
7 | 0 | Normal Headerを示す |
6 | 0 or 1 | Message Type 1: Definition Message 0: Data Message |
5 | 0(default) | Message Type Specific (Developer data flag) |
4 | 0 | Reserved |
0-3 | 0-15 | Local Message Type |
先ほど見ていたデータでは以下のようになってました。仕様と照らし合わせるとDefinition Messageですね。
Timestamp Header
最上位ビット=1がTimestamp Headerと呼ばれるヘッダとのこと
いったん省略(出てきたら追記)
Definition Message
Definition Messageの場合は以下構成を取るようです。
Byte | Description | Length | Value |
---|---|---|---|
0 | Reserved | 1Byte | 0 |
1 | Architecture | 1Byte | Architecture Type 0: Definition and Data Messages are Little Endian 1: Definition and Data Message are Big Endian |
2-3 | Global Message Number | 2Byte | 0:65535 – Unique to each message *Endianness of this 2 Byte value is defined in the Architecture byte |
4 | Fields | 1Byte | Number of fields in the Data Message |
5-4 + Fields * 3 | Field Definition | 3Bytes(per Field) | |
5 + Fields * 3 | # Developer Fields | 1Byte | Number of Self Descriptive fields in the Data Message(Only if Developer Data Flag is set) |
6 + Fields * 3 - END | Developer Field Definition | 3bytes(per Field) |
Architecture
実際のデータを見てみます。まずはArchitectureから
0なので、Little Endianですね。
Global Message Number
次にGlobal Message Numberは0でした。このファイルの一番初めのデータだからですね。
Fields
Field Definition
さて次が少々難解ですが、5-4 + Fields * 3ってどういうことなんでしょうね。フィールドは7個あるということだったので、5byte目から4+7*3=25byte目までってことなんでしょうかね。
↓Field Definitionと思われる範囲
まあ1フィールドは3byteずつということなので、一つ一つ見ていきましょう。
各フィールドの定義は以下
Byte | Name | Description |
---|---|---|
0 | Field Definition Number | Defined in the Global FIT profile for the specified FIT message |
1 | Size | Size (in bytes) of the specified FIT message’s field |
2 | Base type | Base type of the specified FIT message’s field |
ここでField Definition Numberってやつが公式のドキュメント読んでもよくわからないんですが、計測値の種類?を一意に識別する番号のようです。SDK見てねと書いてあったので、見たら理解が深まるかもしれません。 | ||
次のSizeはそのままですね。最後のBase Typeってやつもよくわからんですが、データの型を定義するフィールドのようです(unsigned charとかsigned shortとか)。 | ||
見方は以下表のとおりらしいんですが、正直よくわかりません。 |
じゃあ実際のデータの方を見てみます。
Field Definitionの1byte目、Field Definition Numberは3。
Field Definitionの2byte目、Sizeは4。
Field Definitionの3byte目は0x8Cです。
これをfit.h上の定義で探すと、
FIT_BASE_TYPE_UINT32Zってやつらしいですね。
こんな感じで全7個のフィールドを紐解いてみたところ、以下のようになりました。
# | Field Definition Number | Size | BaseType |
---|---|---|---|
0 | 3 (SN) | 4 | 0x8C (FIT_BASE_TYPE_UINT32) |
1 | 4 (timeらしい) | 4 | 0x86 (FIT_BASE_TYPE_UINT32) |
2 | 7 | 4 | 0x86 (FIT_BASE_TYPE_UINT32) |
3 | 1 (mfg) | 2 | 0x84 (FIT_BASE_TYPE_UINT16) |
4 | 2 (prod) | 2 | 0x84 (FIT_BASE_TYPE_UINT16) |
5 | 5 | 2 | 0x84 (FIT_BASE_TYPE_UINT16) |
6 | 0 (type) | 1 | 0x00 (FIT_BASE_TYPE_ENUM) |
やはりField Definition Numberが謎すぎますね・・・。これについてはまだ正直理解が追い付いてません。すいません。 |
Developer Fields & Developer Field Definition
さて、Definition Messageの最後、# Developer FieldsとDeveloper Field Definitionですが、これはDeveloper Data Flagがセットされているときのみ有効とのことです。Developer Data Flagとは、Record Headerにおける5bit目を指すようです。今見ているデータはフラグ0でしたので今は無視します。
ここまでがDefinition Messageに含まれる情報です。
Data Message
ようやくデータそのものにたどり着きました。Data Messageは別のレコードとして来るので、新たに6bit目が0のレコードヘッダがあるはずです。
OK。想定通りですね。
ここからはField Definitionで定義されたFieldの数、サイズ別にデータが記録されているはずです。
試しに一つ目のデータを見てみます。データサイズは4byte, UINT32ですね。
※シリアル番号らしいので、一応消しておきます。
他のFieldも見てみた結果が以下です。
# | Field Definition Number | Size | BaseType | Value |
---|---|---|---|---|
0 | 3 (SerianNumber) | 4 | 0x8C (FIT_BASE_TYPE_UINT32) | 3347XXXXXX |
1 | 4 (time?) | 4 | 0x86 (FIT_BASE_TYPE_UINT32) | 976363898 |
2 | 7 (?) | 4 | 0x86 (FIT_BASE_TYPE_UINT32) | 4294967295 |
3 | 1 (manufacturer) | 2 | 0x84 (FIT_BASE_TYPE_UINT16) | 1 |
4 | 2 (product) | 2 | 0x84 (FIT_BASE_TYPE_UINT16) | 3779 |
5 | 5 (?) | 2 | 0x84 (FIT_BASE_TYPE_UINT16) | 65535 |
6 | 0 (type) | 1 | 0x00 (FIT_BASE_TYPE_ENUM) | 4 |
ちなみに、製品に同梱されていたシリアルナンバーは上記のシリアルナンバーとは違うものでしたが、
Garmin Connectで製品情報を見た際のURLがこのシリアルナンバーと同じでした。
内部的な管理のための番号のようですね。
まだ理解が追い付いてないところもありますが、通しでデータを見てみてFitデータについて少し理解深めることができた気がします。
次は、SDKを利用して実際にFitデータを読み込み、解析するようなことをしてみたいですね。