ในบทความนี้ คุณจะได้เรียนรู้เกี่ยวกับเมธอด pack และ unpack ของ Ruby!
แต่เราต้องการเมธอดเหล่านี้เพื่ออะไร?
การทำงานกับข้อความง่ายกว่าการทำงานกับข้อมูลไบนารีอย่างมาก
ข้อความช่วยให้คุณใช้:
- Regular expressions
- เมธอดอย่าง
scanและmatch gsub
แต่หากคุณต้องการทำงานกับข้อมูลไบนารี จะต้องทำงานเพิ่มเติมเล็กน้อย นั่นคือที่มาของเมธอด Array#pack และ String#unpack
มาเริ่มจากตัวอย่างง่ายๆ กับสตริงธรรมดา ก่อนจะไปสู่หัวข้อที่น่าสนใจกว่า
แปลงสตริงเป็นค่า ASCII
โค้ดนี้จะแปลงทุกตัวอักษรในสตริงเป็นค่าทศนิยม:
str = "AABBCC"
str.unpack("c*") # [65, 65, 66, 66, 67, 67]
สังเกตอาร์กิวเมนต์ "c*" สำหรับ unpack
นี่คือ "format string" ที่บอก unpack ว่าจะจัดการข้อมูลอย่างไร ในที่นี้ c หมายถึงการนำตัวอักษรหนึ่งตัวมาแปลงเป็นค่าจำนวนเต็ม (เมธอด String#ord ทำสิ่งเดียวกันได้)
เครื่องหมายดอกจัน * หมายถึง "ทำซ้ำรูปแบบนี้สำหรับข้อมูลนำเข้าทั้งหมด"
แปลงค่า Hex เป็นสตริง
การใช้ H กับเมธอด pack จะให้การแปลงจากเลขฐานสิบหกเป็นสตริง
ตัวอย่าง:
["414243"].pack("H*") # "ABC"
"ABC".unpack("H*") # ["414243"]
วิธีแปลง Hex เป็น Integer
Format string นี้รับข้อมูล 4 ไบต์และคืนค่าเป็นจำนวนเต็ม สิ่งหนึ่งที่ต้องสังเกตคือ ไบต์เหล่านี้อยู่ในรูปแบบ "little-endian"
ตัวอย่าง:
"\xff\x00\x00\x00".unpack("l").first # 255
"\x90\xC0\xDD\x08".unpack("l").first # 148750480
ผมใช้ first ที่นี่เพราะ unpack คืนค่าเป็นอาร์เรย์
การแยกวิเคราะห์ไฟล์ไบนารีด้วยเมธอด Unpack
ล่ะจะอ่านไฟล์ไบนารีอย่าง EXE, PNG หรือ GZIP ได้อย่างไร?
ถ้าคุณอ่านไฟล์เหล่านี้เหมือนข้อความธรรมดา คุณจะเห็นสิ่งที่ดูเหมือนข้อมูลสุ่ม...

มันไม่ได้เป็นข้อมูลสุ่มจริงๆ
หลายรูปแบบไฟล์มี โครงสร้างที่มีเอกสารอธิบาย และเมธอด unpack คือเครื่องมือที่คุณสามารถใช้เพื่ออ่านข้อมูลนั้นและแปลงเป็นประโยชน์ได้
นี่คือตัวอย่าง:
binary_data = "\x05\x00\x68\x65\x6c\x6c\x6f"
length, message = binary_data.unpack("Sa*") # [5, "hello"]
ในตัวอย่างนี้ ข้อมูลไบนารี (แสดงในรูปแบบฐานสิบหก ซึ่งกระชับกว่าศูนย์และหนึ่งมาก) มีฟิลด์ความยาว 2 ไบต์ (16 บิต) ที่เก็บความยาวของสตริงที่ตามมา จากนั้นคือสตริง itu เอง
การมีฟิลด์ "ความยาว" เป็นเรื่องปกติมากสำหรับไฟล์ไบนารีและโปรโตคอลเครือข่ายไบนารี
มันบอกพาร์เซอร์ว่าต้องอ่านกี่ไบต์พอดี
ใช่แล้ว
ผมรู้ว่าในตัวอย่างนี้ผมอ่านความยาวและข้อมูลในขั้นตอนเดียว เพื่อให้ง่ายต่อการเข้าใจ
วิธีใช้ Gem BinData
ยังมี bindata gem ที่สร้างขึ้นมาโดยเฉพาะเพื่อช่วยแยกวิเคราะห์โครงสร้างไบนารี
ตัวอย่าง:
class BinaryString < BinData::Record
endian :little
uint16 :len
string :name, read_length: :len
end
สังเกตพารามิเตอร์ read_length จะบอก bindata ให้คำนวณความยาวจากฟิลด์ อะไรนี้จะช่วยประหยัดงานมากเลย 🙂
ดังนั้นถ้าคุณอยากเขียนพาร์เซอร์สำหรับรูปแบบไบนารีใดๆ ขั้นตอนคือ:
- หาสเปกของรูปแบบนั้น (ถ้าไม่ได้เปิดเผยสาธารณะ คุณต้อง reverse-engineer เอง ซึ่งเป็นหัวข้อใหญ่อีกเรื่อง)
- เขียนคลาส
bindataสำหรับแต่ละส่วนของไฟล์ (มักจะเจอส่วน Header ก่อนที่มีเมตาดาต้า แล้วตามด้วยหลายส่วนข้อมูล) - อ่านข้อมูลและประมวลผลอย่างต้องการ (เช่น ใน PNG คุณอาจเปลี่ยนสีของภาพ)
- สำเร็จ!
ถ้าอยากดูตัวอย่างเต็มๆ ของ bindata ลองดู PNG parser ของผมบน GitHub
การเข้ารหัส Base64
มีการเข้ารหัสแบบหนึ่งชื่อ "Base64" คุณอาจเคยเห็นมันบน URL
มันหน้าตาประมาณนี้:
U2VuZCByZWluZm9yY2VtZW50cw==
เครื่องหมาย equals คู่ที่ท้ายมักเป็นสัญญาณบอกว่านี่คือ Base64 แม้บางอินพุทอาจไม่มีเครื่องหมาย equals (ใช้เป็น padding)
ทำไมผมถึงบอกคุณเรื่องนี้...
นอกจากเป็นความรู้ที่มีประโยชน์ด้วยตัวมันเอง?
จริงๆ แล้วคุณสามารถแปลงสตริงเป็น Base64 ด้วยเมธอด pack ได้
ดูตัวอย่าง:
def encode64(bin)
[bin].pack("m")
end
encode64 "abcd" # "YWJjZA==\n"
จริงๆ แล้ววิธีนี้คือเมธอดที่ใช้ในมอดูล Base64 ของ standard library เลย
สรุป
ในบทความนี้ คุณเรียนรู้เกี่ยวกับเมธอด pack และ unpack ซึ่งช่วยให้คุณทำงานกับข้อมูลไบนารีได้ คุณสามารถใช้มันแยกวิเคราะห์ไฟล์ไบนารี แปลงสตริงเป็นค่า ASCII และเข้ารหัส Base64
อย่าลืม แชร์และสมัครรับข่าวสาร เพื่อให้ได้อ่านบทความแบบนี้ต่อไปนะครับ! 🙂