Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Thành thạo Pack & Unpack trong Ruby: Hướng dẫn chuyên gia xử lý dữ liệu nhị phân

Trong bài viết này, bạn sẽ tìm hiểu về các phương thức pack và unpack của Ruby!

Nhưng tại sao chúng ta cần các phương thức này?

Làm việc với văn bản dễ dàng hơn nhiều so với dữ liệu nhị phân.

Văn bản cho phép bạn sử dụng:

  • biểu thức chính quy (regular expressions)
  • các phương thức như scan và match
  • gsub

Nhưng nếu bạn muốn làm việc với dữ liệu nhị phân, có một chút công việc bổ sung cần làm. Đó chính là lúc phương thức Array#pack và String#unpack phát huy tác dụng.

Hãy cùng xem một số ví dụ, bắt đầu từ một chuỗi đơn giản rồi chuyển sang những ví dụ thú vị hơn.

Chuyển chuỗi thành giá trị ASCII

Đoạn mã này sẽ chuyển đổi mỗi ký tự trong chuỗi thành giá trị thập phân:

str = "AABBCC"
str.unpack("c*") # [65, 65, 66, 66, 67, 67]

Chú ý đối số "c*" cho phương thức unpack.

Đây là một "chuỗi định dạng" (format string) chỉ cho unpack biết cách xử lý dữ liệu. Trong trường hợp này, c nghĩa là lấy một ký tự và chuyển đổi thành giá trị nguyên (phương thức String#ord cũng làm điều tương tự).

Dấu hoa thị * chỉ có nghĩa là "lặp lại định dạng này cho tất cả dữ liệu đầu vào".

Chuyển đổi Hex thành Chuỗi

Ký tự H kết hợp với phương thức pack cho phép chuyển đổi số hex thành chuỗi.

Ví dụ:

["414243"].pack("H*") # "ABC"
"ABC".unpack("H*") # ["414243"]

Cách chuyển đổi Hex thành Số nguyên

Chuỗi định dạng này lấy 4 byte dữ liệu và trả về một số nguyên. Điều cần lưu ý là các byte này ở định dạng "little-endian".

Các ví dụ:

"\xff\x00\x00\x00".unpack("l").first # 255
"\x90\xC0\xDD\x08".unpack("l").first # 148750480

Tôi dùng first ở đây vì unpack trả về một mảng.

Phân tích tệp nhị phân với phương thức Unpack

Làm thế nào để đọc một tệp nhị phân như EXE, PNG hoặc GZIP?

Nếu bạn đọc các tệp này như văn bản thuần túy, bạn sẽ thấy thứ gì đó trông như dữ liệu ngẫu nhiên...

Thành thạo Pack & Unpack trong Ruby: Hướng dẫn chuyên gia xử lý dữ liệu nhị phân

Nó không phải là thứ ngẫu nhiên.

Có một cấu trúc được tài liệu hóa cho nhiều định dạng tệp này và phương thức unpack chính là công cụ bạn có thể dùng để đọc dữ liệu đó và chuyển đổi thành thứ gì đó hữu ích.

Dưới đây là một ví dụ:

binary_data = "\x05\x00\x68\x65\x6c\x6c\x6f"
length, message = binary_data.unpack("Sa*") # [5, "hello"]

Trong ví dụ này, dữ liệu nhị phân (được biểu diễn dưới dạng hex, gọn nhẹ hơn nhiều so với 0 và 1) có một trường độ dài 2 byte (16 bit) chứa độ dài của chuỗi đi kèm. Sau đó là chính chuỗi đó.

Việc có trường "độ dài" rất phổ biến trong các tệp nhị phân và giao thức mạng nhị phân.

Điều này cho trình phân tích biết chính xác cần đọc bao nhiêu byte.

Đúng vậy.

Tôi biết trong ví dụ này tôi đọc cả độ dài và dữ liệu trong một bước, chỉ để giữ cho mọi thứ đơn giản thôi.

Cách sử dụng Gem BinData

Còn có gem bindata, được xây dựng đặc biệt để giúp bạn phân tích các cấu trúc nhị phân.

Đây là một ví dụ:

class BinaryString < BinData::Record
  endian :little
  uint16 :len
  string :name, :read_length => :len
end

Chú ý tham số read_length. Nó sẽ nói cho bindata biết phải tính độ dài từ trường đó, giúp bạn tiết kiệm rất nhiều công sức 🙂

Vì vậy, nếu bạn muốn viết trình phân tích cho bất kỳ định dạng nhị phân nào, đây là các bước:

  1. Tìm thông số kỹ thuật cho định dạng đó (nếu không công khai, bạn sẽ phải reverse-engineer, đây là một chủ đề hoàn toàn riêng)
  2. Viết một class bindata cho mỗi phần của tệp (thường bạn sẽ tìm thấy phần header trước với metadata và sau đó là nhiều phần dữ liệu)
  3. Đọc dữ liệu và xử lý theo ý muốn (ví dụ, trong PNG bạn có thể thay đổi màu sắc của hình ảnh)
  4. Thưởng thức thành quả!

Nếu bạn muốn xem ví dụ đầy đủ của bindata trong thực tế, hãy xem trình phân tích PNG của tôi trên GitHub.

Mã hóa Base64

Có loại mã hóa này gọi là "Base64". Bạn có thể đã thấy nó trên URL.

Trông giống như thế này:

U2VuZCByZWluZm9yY2VtZW50cw==

Dấu bằng kép ở cuối thường là dấu hiệu nhận biết bạn đang đối mặt với Base64, mặc dù một số đầu vào có thể không có dấu bằng (chúng được dùng làm padding).

Vậy tại sao tôi nói về điều này...

Ngoài việc nó là kiến thức hữu ích bản thân?

Thực ra, bạn có thể chuyển đổi một chuỗi thành Base64 bằng phương thức pack.

Như bạn thấy ở đây:

def encode64(bin)
  [bin].pack("m")
end
encode64 "abcd" # "YWJjZA==\n"

Trên thực tế, đây chính là phương thức được dùng trong module Base64 của thư viện chuẩn.

Tóm tắt

Trong bài viết này, bạn đã học về các phương thức pack và unpack, giúp bạn làm việc với dữ liệu nhị phân. Bạn có thể dùng chúng để phân tích tệp nhị phân, chuyển chuỗi thành giá trị ASCII, và mã hóa Base64.

Đừng quên chia sẻ và đăng ký để đọc thêm nhiều bài viết như thế này! 🙂