protocol buffersバイナリデータを読む

目的

protocol buffersエンコーディングされたバイナリデータを読んでみます。

準備

この記事では、Protocol Buffer Basics: Pythonのコードを使用します。


addressbook.proto

syntax = "proto2";

package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}


writing_a_message.py

#! /usr/bin/python

import addressbook_pb2
import sys

# This function fills in a Person message based on user input.
def PromptForAddress(person):
  person.id = int(raw_input("Enter person ID number: "))
  person.name = raw_input("Enter name: ")

  email = raw_input("Enter email address (blank for none): ")
  if email != "":
    person.email = email

  while True:
    number = raw_input("Enter a phone number (or leave blank to finish): ")
    if number == "":
      break

    phone_number = person.phones.add()
    phone_number.number = number

    type = raw_input("Is this a mobile, home, or work phone? ")
    if type == "mobile":
      phone_number.type = addressbook_pb2.Person.MOBILE
    elif type == "home":
      phone_number.type = addressbook_pb2.Person.HOME
    elif type == "work":
      phone_number.type = addressbook_pb2.Person.WORK
    else:
      print "Unknown phone type; leaving as default value."

# Main procedure:  Reads the entire address book from a file,
#   adds one person based on user input, then writes it back out to the same
#   file.
if len(sys.argv) != 2:
  print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"
  sys.exit(-1)

address_book = addressbook_pb2.AddressBook()

# Read the existing address book.
try:
  f = open(sys.argv[1], "rb")
  address_book.ParseFromString(f.read())
  f.close()
except IOError:
  print sys.argv[1] + ": Could not open file.  Creating a new one."

# Add an address.
PromptForAddress(address_book.people.add())

# Write the new address book back to disk.
f = open(sys.argv[1], "wb")
f.write(address_book.SerializeToString())
f.close()


reading_a_message.py

#! /usr/bin/python

import addressbook_pb2
import sys

# Iterates though all people in the AddressBook and prints info about them.
def ListPeople(address_book):
  for person in address_book.people:
    print "Person ID:", person.id
    print "  Name:", person.name
    if person.HasField('email'):
      print "  E-mail address:", person.email

    for phone_number in person.phones:
      if phone_number.type == addressbook_pb2.Person.MOBILE:
        print "  Mobile phone #: ",
      elif phone_number.type == addressbook_pb2.Person.HOME:
        print "  Home phone #: ",
      elif phone_number.type == addressbook_pb2.Person.WORK:
        print "  Work phone #: ",
      print phone_number.number

# Main procedure:  Reads the entire address book from a file and prints all
#   the information inside.
if len(sys.argv) != 2:
  print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"
  sys.exit(-1)

address_book = addressbook_pb2.AddressBook()

# Read the existing address book.
f = open(sys.argv[1], "rb")
address_book.ParseFromString(f.read())
f.close()

ListPeople(address_book)

解析

以上のコードよりADDRESS_BOOK_FILEを作成します。

% python reading_a_message.py ADDRESS_BOOK_FILE

Person ID: 1
  Name: jone
  E-mail address: jone@example.com
  Mobile phone #:  000-0000-0001
Person ID: 2
  Name: mike
  E-mail address: mike@example.com
  Home phone #:  000-0000-0002
  Work phone #:  000-0000-0022

ADDRESS_BOOK_FILEをhexdumpすると、以下のようになります。

% hexdump -C ADDRESS_BOOK_FILE
00000000  0a 2d 0a 04 6a 6f 6e 65  10 01 1a 10 6a 6f 6e 65  |.-..jone....jone|
00000010  40 65 78 61 6d 70 6c 65  2e 63 6f 6d 22 11 0a 0d  |@example.com"...|
00000020  30 30 30 2d 30 30 30 30  2d 30 30 30 31 10 00 0a  |000-0000-0001...|
00000030  40 0a 04 6d 69 6b 65 10  02 1a 10 6d 69 6b 65 40  |@..mike....mike@|
00000040  65 78 61 6d 70 6c 65 2e  63 6f 6d 22 11 0a 0d 30  |example.com"...0|
00000050  30 30 2d 30 30 30 30 2d  30 30 30 32 10 01 22 11  |00-0000-0002..".|
00000060  0a 0d 30 30 30 2d 30 30  30 30 2d 30 30 32 32 10  |..000-0000-0022.|
00000070  02                                                |.|
00000071

ADDRESS_BOOK_FILEをEncodingに従って解析すると以下のようになります。

アドレス 00000000 00000001 00000002 00000003 00000004 00000005 00000006 00000007
データ
0a
2d
0a
04
6a
6f
6e
65
内容 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(people) payload size (0x2d bytes) (jone分) 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(name) 文字列は4 byte j o n e
アドレス 00000008 00000009 0000000a 0000000b 0000000c 0000000d 0000000e 0000000f
データ
10
01
1a
10
6a
6f
6e
65
内容 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(id) id(jone) = 1 0x00011010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0011(email) 文字列は0x10 byte j o n e
アドレス 00000010 00000011 00000012 00000013 00000014 00000015 00000016 00000017
データ
40
65
78
61
6d
70
6c
65
内容 @ e x a m p l e
アドレス 00000018 00000019 0000001a 0000001b 0000001c 0000001d 0000001e 0000001f
データ
2e
63
6f
6d
22
11
0a
0d
内容 . c o m 0x00100010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0100(phones) payload size (0x11 bytes) 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(number) 文字列は0x0d byte
アドレス 00000020 00000021 00000022 00000023 00000024 00000025 00000026 00000027
データ
30
30
30
2d
30
30
30
30
内容 0 0 0 - 0 0 0 0
アドレス 00000028 00000029 0000002a 0000002b 0000002c 0000002d 0000002e 0000002f
データ
2d
30
30
30
31
10
00
0a
内容 - 0 0 0 1 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(type) PhoneType = 0(MOBILE) 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(people)
アドレス 00000030 00000031 00000032 00000033 00000034 00000035 00000036 00000037
データ
40
0a
04
6d
69
6b
65
10
内容 payload size (0x40 bytes) (mike分) 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(name) 文字列は4 byte m i k e 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(id)
アドレス 00000038 00000039 0000003a 0000003b 0000003c 0000003d 0000003e 0000003f
データ
02
1a
10
6d
69
6b
65
40
内容 id(mike) = 2 0x00011010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0011(email) 文字列は0x10 byte m i k e @
アドレス 00000040 00000041 00000042 00000043 00000044 00000045 00000046 00000047
データ
65
78
61
6d
70
6c
65
2e
内容 e x a m p l e .
アドレス 00000048 00000049 0000004a 0000004b 0000004c 0000004d 0000004e 0000004f
データ
63
6f
6d
22
11
0a
0d
30
内容 c o m 0x00100010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0100(phones) payload size (0x11 bytes) 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(number) 文字列は0x0d byte 0
アドレス 00000050 00000051 00000052 00000053 00000054 00000055 00000056 00000057
データ
30
30
2d
30
30
30
30
2d
内容 0 0 - 0 0 0 0 -
アドレス 00000058 00000059 0000005a 0000005b 0000005c 0000005d 0000005e 0000005f
データ
30
30
30
32
10
01
22
11
内容 0 0 0 2 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(type) PhoneType = 1(HOME) 0x00100010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0100(phones) payload size (0x11 bytes)
アドレス 00000060 00000061 00000062 00000063 00000064 00000065 00000066 00000067
データ
0a
0d
30
30
30
2d
30
30
内容 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(number) 文字列は0x0d byte 0 0 0 - 0 0
アドレス 00000068 00000069 0000006a 0000006b 0000006c 0000006d 0000006e 0000006f
データ
30
30
2d
30
30
32
32
10
内容 0 0 - 0 0 2 2 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(type)
アドレス 00000070
データ
02
内容 PhoneType = 2(WORK)


バイナリって楽しいですね。