الیکسیر برای کار با دادههای دودویی شکل نگارشی زیبایی در اختیارمان میگذارد؛ همانطور که در فرم ویژهی <<>> که برای کار با «رشتههای بیت» فراهم شده است دیدهایم.
نوع «دودویی» حالت خاصی از نوع «رشتهی بیت» است. رشتههای بیت میتوانند هر طولی داشته باشند (هر تعداد بیت)، حال آنکه در دودوییها تعداد بیتها بر ۸ بخشپذیر است. یعنی هنگام کار با دودوییها معمولاً در قالب «بایت» (۸ بیت) به دادهها نگاه میکنیم. یک بایت میتواند اعداد صحیحی از 0 تا 255 را نمایش دهد. کار با مقدارهای بایتی در مبنای شانزده رایج است، مثل 0x00 - 0xFF.
لیترالهای دودویی با فرم ویژهی رشتهی بیت <<>> تعریف میشوند. هنگام تعریف یک لیترال دودویی، میتوانیم از لیترالهای عددی و رشتهای استفاده کنیم. مقدارهای عددی بزرگتر از ۲۵۵ سرریز میشوند و تنها ۸ بیت آخر عدد به کار میرود. بهطور پیشفرض، اصلاحگر ::binary روی مقدار اعمال میشود. میتوانیم دودوییها را با عملگر <>/2 به هم بچسبانیم.
<<255>> == <<0xFF>>
# Overflowing bits are truncated
<<256>> == <<0>>
<<2, 4, 6, 8, 10, 12, 14, 16>> == <<0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10>>
«بایت تهی» نام دیگری برای <<0>> است.
تطبیق الگو حتی به دودوییها هم گسترش یافته است و میتوانیم روی بخشی از دادههای دودویی تطبیق الگو انجام دهیم، درست همانطور که این کار را برای یک لیست انجام میدادیم.
# Ignore the first 8 bytes, match and bind the remaining to `body`
<<_::binary-size(8), body::binary>>
مثل سایر انواع تطبیق الگو، میتوانیم از این قابلیت در امضای توابع هم استفاده کنیم و هنگام انتخاب از میان چند بند تابع، تطبیق الگو انجام دهیم.
شما روی پروژهای کار میکنید که به کاربران اجازه میدهد فایلها را روی سرور بارگذاری کنند تا با کاربران دیگر به اشتراک گذاشته شود. به شما سپرده شده است که تابعی بنویسید که بررسی کند یک فایل بارگذاریشده با نوع رسانه آن مطابقت دارد یا نه. کمی تحقیق میکنید و متوجه میشوید که چند بایت اول یک فایل معمولاً مخصوص همان نوع فایل است و به آن نوعی امضا میدهد.
برای مرجع از جدول زیر استفاده کنید:
| نوع فایل | پسوند رایج | نوع رسانه | «امضای دودویی» |
|---|---|---|---|
| ELF | "exe" |
"application/octet-stream" |
0x7F, 0x45, 0x4C, 0x46 |
| BMP | "bmp" |
"image/bmp" |
0x42, 0x4D |
| PNG | "png" |
"image/png" |
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A |
| JPG | "jpg" |
"image/jpg" |
0xFF, 0xD8, 0xFF |
| GIF | "gif" |
"image/gif" |
0x47, 0x49, 0x46 |
تابع type_from_extension/1 را پیادهسازی کنید. این تابع باید یک پسوند فایل (رشته) بگیرد و نوع رسانه (رشته) یا nil را برگرداند، اگر پسوند با پسوندهای مورد انتظار مطابقت نداشته باشد.
FileSniffer.type_from_extension("exe")
# => "application/octet-stream"
FileSniffer.type_from_extension("txt")
# => nil
تابع type_from_binary/1 را پیادهسازی کنید. این تابع باید یک فایل (دودویی) بگیرد و نوع رسانه (رشته) یا nil را برگرداند، اگر امضای فایل با امضاهای مورد انتظار مطابقت نداشته باشد.
file = File.read!("application.exe")
FileSniffer.type_from_binary(file)
# => "application/octet-stream"
file = File.read!("example.txt")
FileSniffer.type_from_binary(file)
# => nil
نگران خواندن فایل بهصورت دودویی نباشید. فرض کنید این کار پیشاپیش برای شما انجام شده است و tests آن را بهعنوان آرگومان در اختیار شما قرار میدهند.
تابع verify/2 را پیادهسازی کنید. این تابع باید یک فایل (دودویی) و یک پسوند (رشته) بگیرد و یک تاپل :ok یا :error برگرداند.
file = File.read!("application.exe")
FileSniffer.verify(file, "exe")
# => {:ok, "application/octet-stream"}
FileSniffer.verify(file, "png")
# => {:error, "Warning, file format and file extension do not match."}