CubicLouve

Spring_MTの技術ブログ

rubyのIPAddrクラスを使えばIPアドレスがCIDR範囲に含まれるかもチェックできる

rubyにはIP アドレスを扱うのためのIPAddrクラスがあります。

docs.ruby-lang.org

このIPAddrクラスを使えば、IPアドレスがCIDR範囲に含まれるかもチェックできます。

irb(main):001:0> require 'ipaddr'
=> true
irb(main):002:0> cidr_sample = IPAddr.new("192.0.2.0/24")
=> #<IPAddr: IPv4:192.0.2.0/255.255.255.0>
irb(main):003:0> cidr_sample.include? "192.0.2.1"
=> true
irb(main):004:0> cidr_sample.include? "192.0.2.255"
=> true
irb(main):005:0> cidr_sample.include? "192.1.2.0"
=> false

便利!

参考

datatracker.ietf.org

MTUについて

MTUとは

MTUは最大転送単位(Maximum Transmission Unit)

MTUはデータリンク層(L2)の性質となる。

RFC 791: Internet Protocol

データリンク層での最大のフレーム長(データリンク層のパケットを表すときにはフレームが使われる。)

MTUはデータリンク層での性質なので、IP層(L3)のヘッダーまでを含んでいる。

IPはデータリンクの上位層なので、MTUを隠蔽する役割を持つ。

そこで、データリンクを司るルーターなどはMTUに応じてIPデータグラムを分割する(IP fragmentation フラグメンテーション)。 (データグラムはIPなどのネットワーク層以上でのパケット単位のデータ構造を持つプロトコルで利用される表現)

IPヘッダーには分割されたパケットを管理するためのフラグがある。

https://packetpushers.net/wp-content/uploads/2019/11/IPv4-Headers-Standard-Fragmentation-Highlighted.png

分割されたIPデータグラムを元のIPデータグラムに戻す再構築の処理は終点の宛先ホストでのみ行われる。

TCPの場合

トランスポート層(L4)でTCPを使う場合、一度にTCPで含められるアプリケーションデータのサイズは、MTUからIPヘッダー(20バイト)とTCPヘッダー(20バイトとオプションがあると60バイトまで増える)を除いたバイト数となる。

このサイズをMSS(Mazimum Segment Size 最大セグメント長)と呼ぶ。(セグメントはTCPに含まれるデータの表すときに使う)

TCPでは3ウェイ・ハンドシェイクのときに、コネクション確立要求を送るときに、送信側、受信側ともに自身のMSSを通知し、小さいMSSを採用する。

MSSに沿って、データを区切って送信されるため、IPでの分割は行わず、TCP側で再構築をする。

再送処理はMSS単位で行われる。

手元でWireSharkで確認してみた例

MSSを相互に通知している様子

MSSによって分割されたデータを再構築している

参考文献

インターネット用語1分解説~MTUとは~ - JPNIC

https://www.cloudflare.com/ja-jp/learning/network-layer/what-is-mtu/

【図解】MTUとMSS, パケット分割の考え方 ~IPフラグメンテーションとTCPセグメンテーション~ | SEの道標

MTUの最適値を調べる方法 | server-memo.net

MySQLにおける外部キー作成時の自動インデックス生成

MySQLの外部キー制約において、外部キーと参照キーにはインデックスが必要です。

下記はMySQL 8.0のドキュメントですが、5.7でも同じような内容となっています。

dev.mysql.com

参照元のテーブルには、外部キーのカラムが同じ順序で最初のカラムとしてならぶインデックスが必要です。 もし該当するindexがなければ、MySQLは自動でインデックスを作成します。 この自動で作られたインデックスは後で追加したインデックスが外部キーのインデックス要件を満たす場合、暗黙的に削除されることがあります。

これを実際に試してみようと思います

確認したMySQLのバージョンは 8.0.29 です。

まずテーブルを作ってみます

mysql> CREATE TABLE `users` (
    ->   `id` int NOT NULL,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE `user_foo` (
    ->   `id` int NOT NULL AUTO_INCREMENT,
    ->   `user_id` int NOT NULL,
    ->   `foo` int  NOT NULL,
    ->   PRIMARY KEY (`id`),
    ->   CONSTRAINT FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.01 sec)

これでテーブル定義を見てみます。

mysql> SHOW CREATE TABLE user_foo;
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                                                                                                                         |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| user_foo | CREATE TABLE `user_foo` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `foo` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`), <----- CREATE文にないindexが追加されている
  CONSTRAINT `user_foo_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

CREATE文にないインデックスが自動で作られます。

外部キーのcolumnが同じ順序で最初のカラムとしてならぶインデックスが必要です。 これを確認してみましょう。

外部キーを含む複数カラムに対してセカンダリインデックスを追加してみます。

mysql> CREATE TABLE `users` (
    ->   `id` int NOT NULL,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE `user_foo` (
    ->   `id` int NOT NULL AUTO_INCREMENT,
    ->   `user_id` int NOT NULL,
    ->   `foo` int  NOT NULL,
    ->   PRIMARY KEY (`id`),
    ->   KEY (`user_id`, `foo`),
    ->   CONSTRAINT FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW CREATE TABLE user_foo;                                                                                                                                                                                     +----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                                                                                                                               |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| user_foo | CREATE TABLE `user_foo` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `foo` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`,`foo`),
  CONSTRAINT `user_foo_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

今度は自動でのインデックスが追加されていません。

user_idとfooのセカンダリインデックスで外部キー制約のインデックスの要件を満たしたためですね。

では今度はuser_idとfooを逆にしてみます。

mysql> CREATE TABLE `users` (
    ->   `id` int NOT NULL,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE `user_foo` (
    ->   `id` int NOT NULL AUTO_INCREMENT,
    ->   `user_id` int NOT NULL,
    ->   `foo` int  NOT NULL,
    ->   PRIMARY KEY (`id`),
    ->   KEY (`foo`, `user_id`),
    ->   CONSTRAINT FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW CREATE TABLE user_foo;
+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                                                                                                                                                        |
+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| user_foo | CREATE TABLE `user_foo` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `foo` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `foo` (`foo`,`user_id`),
  KEY `user_id` (`user_id`),
  CONSTRAINT `user_foo_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

今度は 外部キーのcolumnが同じ順序で最初のカラムとしてならぶインデックスにならなかったため、user_id単独のセカンダリインデックスが自動で追加されました。

今度は外部キー制約を満たすために追加したインデックスが不要になるインデックスが追加されたときの挙動を見てみます。

mysql> CREATE TABLE `users` (
    ->   `id` int NOT NULL,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE `user_foo` (
    ->   `id` int NOT NULL AUTO_INCREMENT,
    ->   `user_id` int NOT NULL,
    ->   `foo` int  NOT NULL,
    ->   PRIMARY KEY (`id`),
    ->   CONSTRAINT FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW CREATE TABLE user_foo;
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                                                                                                                         |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| user_foo | CREATE TABLE `user_foo` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `foo` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`), <---- 自動で追加されたインデックス
  CONSTRAINT `user_foo_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

自動でインデックスが追加されています。

では、新たにインデックスを追加してみます。

mysql> ALTER TABLE user_foo ADD INDEX (`user_id`,`foo`); 
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE user_foo;
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                                                                                                                               |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| user_foo | CREATE TABLE `user_foo` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `foo` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`,`foo`),
  CONSTRAINT `user_foo_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

user_id単体ではられていたインデックスが削除され、ALTERによって追加されたインデックスのみが残りました。

ではこのインデックスを削除してみるとどうなでしょう。

mysql> ALTER TABLE user_foo DROP INDEX `user_id`;
ERROR 1553 (HY000): Cannot drop index 'user_id': needed in a foreign key constraint

この場合は、自動でインデックスが追加されるわけではなく、外部キー制約に必要なるということでエラーになります。

ActiveRecordのcallbackの実装を調べたときのメモ

Railsのcallbackの実体

callbackはActiveSupport::Callbacksを使って定義されます。

github.com

ActiveRecordのcallback

ActiveRecord::Callbacksに定義があります。

github.com

ここでcreateなどが定義されます。

github.com

define_model_callbacksは下記で実装されています。

github.com

例 : after_create hookの呼び出され方

ActiveRecord::Baseを継承したUserクラスを考えます。

Railsのバージョンは6.1.4.6を使っています。

Userクラスにはcreateというクラスメソッドがあり、recordを登録できます。

実体は、

irb(main):002:0> User.method(:create)
=> #<Method: User(id: integer, .....).create(attributes=..., &block) /path/to/..../gems/activerecord-6.1.4.6/lib/active_record/persistence.rb:33>

なので、下記です。

github.com

hookがどのように呼び出されるかがわかりにくいので、下記でbreak pointを仕込んでおきます。

github.com

で、after_commit のcallbackをなにかしら設定しておいて、User.createを実行してみます

irb(main):001:0> User.create(....)

[1] pry(User)> 

  TRANSACTION (0.2ms)  BEGIN

From:  /path/to/..../gems/activerecord-6.1.4.6/lib/active_record/callbacks.rb:461 ActiveRecord::Callbacks#_create_record:

    460: def _create_record
 => 461:   binding.pry
    462:   _run_create_callbacks { super }
    463: end

とまりましたね。 ここでcallerを一部抜粋すると

 "/activerecord-6.1.4.6/lib/active_record/callbacks.rb:461:in `_create_record'",
 "/activerecord-6.1.4.6/lib/active_record/timestamp.rb:108:in `_create_record'",
 "/activerecord-6.1.4.6/lib/active_record/persistence.rb:903:in `create_or_update'",
 "/activerecord-6.1.4.6/lib/active_record/callbacks.rb:457:in `block in create_or_update'",

"/activerecord-6.1.4.6/lib/active_record/persistence.rb:477:in `save'"

saveから callbacksの _create_record が呼ばれて、ここでcallbackが発動することとなります。

after_createはcommit前に発動するので、このcallback内で新しくinsertした内容を別のトランザクションからは取得できないので要注意です。

Railsのrspecのrequest specについて

rspec.info

上記の記事にあるように、Rails 5以降ではrails-controller-testing gemをアプリケーションに追加することは推奨されておらず、RSpecコアチームはrequest specsを書くことを推奨しています。

その理由として、request specでは、単一のcontroller actionをテストしつつ、さらにcontroller specとは異なり、router、middleware、rackのrequestとresponseが含まれることになります。

これにより、書いているテストがより実際のリクエストに近くなり、controller testにありがちな問題を回避することができます。

Rails 5ではRails 4のrequest specやcontroller specに比べてかなりの高速化も行われています。

このように、controller specはよりrequest specを書くことがRails 5以降のデファクトとなっています。

GitHub - rspec/rspec-rails: RSpec for Rails 5+

request spec - Request specs - RSpec Rails - RSpec - Relish

request specの使用

rspec-railsを使った場合、specのtypeをtop-levelのdescribeで指定します。

RSpec.describe User, type: :request do

requestを指定することで、ActionDispatch::IntegrationTestに相当することになります。

api.rubyonrails.org

この中でHTTP requestをシュミレートする get post patch put delete head メソッドを使うことになります。

controller specでも同じようなメソッドを使うことになりますが、呼び出されたメソッドが実行するものが違います。

実装は下記になります。

github.com

この中で、 Rack::Test::Session が使われており、Rails appではなく、Rack appとしてリクエストを処理するようになっています。

github.com

request specはRackのレイヤーまでを網羅したテストとなっています。

ちょっとハマった点

rack appに対するリクエストを組み立てテストを行うのですが、Rack::Test:Session#requestの実装が少し色々やりすぎててハマりました

github.com

このなかで、specで定義したparamsをURL-encoded form dataにエンコードを自動的に行なっているので、テストもとでencodeすると二重でencodeされることになります。

こういった処理をどこまでやるかの判断は難しいですが、自動的に行われることは意識しておいていいかなと思います。

線形合同法(Linear congruential method)のスペクトル検定による可視化

線形合同法擬似乱数生成器の一つ

\displaystyle{
X_{n+1} = (A \times X_{n} + C)  \bmod M
}

ここでのA(乗数) C(増分) M(法)は定数で、AとCはMより小さい数を選ぶ。

このA C Mの選び方によって周期性は変わる。

どういう選び方すればをいいかは下記本を参照ください。

線形合同法で生成された数列がどれくらい乱雑かを判定する方法の一つにスペクトル検定がある。

今回は三次元でこれをグラフにしてみた。

Rubyのrandはメルセンヌ・ツイスタなので、それと線形合同法で生成した乱数の分布を比べてみる。

docs.ruby-lang.org

docs.ruby-lang.org

適当なスクリプトを書いて可視化した結果が下記の図となる。

可視化にはGR.rbを利用している。

github.com

  • 線形合同法(A(乗数)を137、C(増分)を187、M(法)を256にしているので規則性がわざと出やすくしている)

こう見ても今回のパラメータの線形合同法の結果は乱雑さがない感じがする。

参考資料

ja.wikipedia.org

http://www.math.sci.hiroshima-u.ac.jp/m-mat/TEACH/ichimura-sho-koen.pdf

tsujimotter.hatenablog.com

バイナリファイルを見る方法

白く塗りつぶしたpngファイルの例でやってみる。

% convert -size 128x128 xc:white white.png

vimを使う場合

vim -b white.png

でバイナリモードでvimで開いた後に下記コマンドでバイナリダンプします。

:%!xxd
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 0080 0000 0080 0100 0000 00eb 455c  ..............E\
00000020: 6600 0000 0467 414d 4100 00b1 8f0b fc61  f....gAMA......a
00000030: 0500 0000 2063 4852 4d00 007a 2600 0080  .... cHRM..z&...
00000040: 8400 00fa 0000 0080 e800 0075 3000 00ea  ...........u0...
00000050: 6000 003a 9800 0017 709c ba51 3c00 0000  `..:....p..Q<...
00000060: 0262 4b47 4400 01dd 8a13 a400 0000 0774  .bKGD..........t
00000070: 494d 4507 e602 170e 1622 78a1 b740 0000  IME......"x..@..
00000080: 001f 4944 4154 48c7 63f8 8f06 1846 0546  ..IDATH.c....F.F
00000090: 0546 0546 0546 0546 0546 0546 0568 2b00  .F.F.F.F.F.F.h+.
000000a0: 0004 a0f8 6a22 6527 5400 0000 2574 4558  ....j"e'T...%tEX
000000b0: 7464 6174 653a 6372 6561 7465 0032 3032  tdate:create.202
000000c0: 322d 3032 2d32 3354 3134 3a32 323a 3334  2-02-23T14:22:34
000000d0: 2b30 303a 3030 d063 4c5a 0000 0025 7445  +00:00.cLZ...%tE
000000e0: 5874 6461 7465 3a6d 6f64 6966 7900 3230  Xtdate:modify.20
000000f0: 3232 2d30 322d 3233 5431 343a 3232 3a33  22-02-23T14:22:3
00000100: 342b 3030 3a30 30a1 3ef4 e600 0000 0049  4+00:00.>......I
00000110: 454e 44ae 4260 82                        END.B`.

ちなみにバイナリモードでファイルを開かないとバイナリ表示がおかしくなるので注意 下記はバイナリモードで開かなかった場合のバイナリダンプ

00000000: 3f50 4e47 0d0a 1a0a 0000 000d 4948 4452  ?PNG........IHDR
00000010: 0000 003f 0000 003f 0100 0000 003f 455c  ...?...?.....?E\
00000020: 6600 0000 0467 414d 4100 003f 3f0b 3f61  f....gAMA..??.?a
00000030: 0500 0000 2063 4852 4d00 007a 2600 003f  .... cHRM..z&..?
00000040: 3f00 003f 0000 003f 3f00 0075 3000 003f  ?..?...??..u0..?
00000050: 6000 003a 3f00 0017 703f 3f51 3c00 0000  `..:?...p??Q<...
00000060: 0262 4b47 4400 01dd 8a13 3f00 0000 0774  .bKGD.....?....t
00000070: 494d 4507 3f02 170e 1622 783f 3f40 0000  IME.?...."x??@..
00000080: 001f 4944 4154 483f 633f 3f06 1846 0546  ..IDATH?c??..F.F
00000090: 0546 0546 0546 0546 0546 0546 0568 2b00  .F.F.F.F.F.F.h+.
000000a0: 0004 3f3f 6a22 6527 5400 0000 2574 4558  ..??j"e'T...%tEX
000000b0: 7464 6174 653a 6372 6561 7465 0032 3032  tdate:create.202
000000c0: 322d 3032 2d32 3354 3134 3a32 323a 3334  2-02-23T14:22:34
000000d0: 2b30 303a 3030 3f63 4c5a 0000 0025 7445  +00:00?cLZ...%tE
000000e0: 5874 6461 7465 3a6d 6f64 6966 7900 3230  Xtdate:modify.20
000000f0: 3232 2d30 322d 3233 5431 343a 3232 3a33  22-02-23T14:22:3
00000100: 342b 3030 3a30 303f 3e3f 3f00 0000 0049  4+00:00?>??....I
00000110: 454e 443f 4260 3f0a                      END?B`?.

hexdumpコマンドを使う場合

受け取ったデータを8進数や16進数でダンプする。(デフォルト16進数) hexdumpはオプションなしでは、2バイト単位で処理し、リトルエンディアン(最下位のバイトから順番に表示)で表示します。 -C オプションをつけることで1バイトずつ処理をする。

なので、基本 -C オプションを付けて使う。

% hexdump -C white.png
00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 00 80 00 00 00 80  01 00 00 00 00 eb 45 5c  |..............E\|
00000020  66 00 00 00 04 67 41 4d  41 00 00 b1 8f 0b fc 61  |f....gAMA......a|
00000030  05 00 00 00 20 63 48 52  4d 00 00 7a 26 00 00 80  |.... cHRM..z&...|
00000040  84 00 00 fa 00 00 00 80  e8 00 00 75 30 00 00 ea  |...........u0...|
00000050  60 00 00 3a 98 00 00 17  70 9c ba 51 3c 00 00 00  |`..:....p..Q<...|
00000060  02 62 4b 47 44 00 01 dd  8a 13 a4 00 00 00 07 74  |.bKGD..........t|
00000070  49 4d 45 07 e6 02 17 0e  16 22 78 a1 b7 40 00 00  |IME......"x..@..|
00000080  00 1f 49 44 41 54 48 c7  63 f8 8f 06 18 46 05 46  |..IDATH.c....F.F|
00000090  05 46 05 46 05 46 05 46  05 46 05 46 05 68 2b 00  |.F.F.F.F.F.F.h+.|
000000a0  00 04 a0 f8 6a 22 65 27  54 00 00 00 25 74 45 58  |....j"e'T...%tEX|
000000b0  74 64 61 74 65 3a 63 72  65 61 74 65 00 32 30 32  |tdate:create.202|
000000c0  32 2d 30 32 2d 32 33 54  31 34 3a 32 32 3a 33 34  |2-02-23T14:22:34|
000000d0  2b 30 30 3a 30 30 d0 63  4c 5a 00 00 00 25 74 45  |+00:00.cLZ...%tE|
000000e0  58 74 64 61 74 65 3a 6d  6f 64 69 66 79 00 32 30  |Xtdate:modify.20|
000000f0  32 32 2d 30 32 2d 32 33  54 31 34 3a 32 32 3a 33  |22-02-23T14:22:3|
00000100  34 2b 30 30 3a 30 30 a1  3e f4 e6 00 00 00 00 49  |4+00:00.>......I|
00000110  45 4e 44 ae 42 60 82                              |END.B`.|
00000117

xxdコマンドを使う場合

ファイルを16進数でダンプする。 復元もできるのがポイント。

% xxd white.png
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 0080 0000 0080 0100 0000 00eb 455c  ..............E\
00000020: 6600 0000 0467 414d 4100 00b1 8f0b fc61  f....gAMA......a
00000030: 0500 0000 2063 4852 4d00 007a 2600 0080  .... cHRM..z&...
00000040: 8400 00fa 0000 0080 e800 0075 3000 00ea  ...........u0...
00000050: 6000 003a 9800 0017 709c ba51 3c00 0000  `..:....p..Q<...
00000060: 0262 4b47 4400 01dd 8a13 a400 0000 0774  .bKGD..........t
00000070: 494d 4507 e602 170e 1622 78a1 b740 0000  IME......"x..@..
00000080: 001f 4944 4154 48c7 63f8 8f06 1846 0546  ..IDATH.c....F.F
00000090: 0546 0546 0546 0546 0546 0546 0568 2b00  .F.F.F.F.F.F.h+.
000000a0: 0004 a0f8 6a22 6527 5400 0000 2574 4558  ....j"e'T...%tEX
000000b0: 7464 6174 653a 6372 6561 7465 0032 3032  tdate:create.202
000000c0: 322d 3032 2d32 3354 3134 3a32 323a 3334  2-02-23T14:22:34
000000d0: 2b30 303a 3030 d063 4c5a 0000 0025 7445  +00:00.cLZ...%tE
000000e0: 5874 6461 7465 3a6d 6f64 6966 7900 3230  Xtdate:modify.20
000000f0: 3232 2d30 322d 3233 5431 343a 3232 3a33  22-02-23T14:22:3
00000100: 342b 3030 3a30 30a1 3ef4 e600 0000 0049  4+00:00.>......I
00000110: 454e 44ae 4260 82                        END.B`.

xxd -r でdumpしたファイルから復元して新しいデータとして保存する