====== Go Stdlib Wiki: package encoding/binary ====== ===== 1. What it is ===== encoding/binary is a Go standard library package for encoding/decoding fixed-size numeric data (and structs made of fixed-size fields) to/from raw bytes—often used when you need a specific binary layout for files, network protocols, or hardware formats. binary /ˈbaɪnəri/ = nhị phân encode /ɪnˈkoʊd/ = mã hoá (đóng gói dữ liệu) decode /diːˈkoʊd/ = giải mã (mở gói dữ liệu) fixed-size /ˌfɪkst ˈsaɪz/ = kích thước cố định raw bytes /rɔː baɪts/ = byte thô ===== 2. What it is for ===== Use encoding/binary when you want to: Read/write numbers in a specific endianness (Little/Big endian) from/to a []byte or stream. Serialize/deserialize simple structs with fixed-size numeric fields (careful with padding/alignment). Implement or parse binary protocols (e.g., custom TCP protocol headers). Work with binary file formats (headers, frames, records). endianness /en.di.ˈæn.nəs/ = thứ tự sắp byte (little/big endian) protocol /ˈproʊtəˌkɔːl/ = giao thức serialize /ˈsɪrəˌlaɪz/ = tuần tự hoá (biến dữ liệu thành chuỗi byte) ===== 3. What it is NOT for ===== encoding/binary is not ideal for: Complex object serialization (maps with variable lengths, nested slices, strings, etc.). Cross-language “general-purpose” formats (use protobuf/CBOR/MessagePack/JSON depending on needs). Human-readable output. Why: binary.Write/binary.Read require fixed-size types. Strings/slices need a length prefix you must design yourself. ===== 4. Core concepts ===== ==== 4.1 Byte order (LittleEndian vs BigEndian) ==== encoding/binary exposes byte orders: binary.LittleEndian binary.BigEndian Rule of thumb: Many network protocols use Big Endian (“network byte order”). Many CPU-native/file formats may use Little Endian. network byte order /ˈnetˌwɝːk baɪt ˈɔːrdər/ = thứ tự byte chuẩn mạng import "encoding/binary" var x uint32 = 0x11223344 buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, x) // [11 22 33 44] in hex binary.LittleEndian.PutUint32(buf, x) // [44 33 22 11] in hex ==== 4.2 Working with numbers in a byte slice ==== Use PutUintXX / UintXX for fast conversion. b := make([]byte, 8) binary.LittleEndian.PutUint16(b[0:2], 513) // write v16 := binary.LittleEndian.Uint16(b[0:2]) // read binary.BigEndian.PutUint64(b[0:8], 123456789) // write v64 := binary.BigEndian.Uint64(b[0:8]) // read uint16/uint64 /ˌjuːɪnt sɪkˈstiːn/ = số nguyên không dấu 16/64-bit ===== 5. binary.Read and binary.Write ===== These operate on an io.Reader / io.Writer and encode/decode using a chosen byte order. Signatures (conceptually): binary.Read(r io.Reader, order ByteOrder, data any) error binary.Write(w io.Writer, order ByteOrder, data any) error stream /striːm/ = luồng dữ liệu reader /ˈriːdər/ = bộ đọc writer /ˈraɪtər/ = bộ ghi ==== 5.1 Example: write then read a struct header ==== Important constraints: Fields should be fixed-size numeric types (e.g., uint32, int16, [16]byte). Avoid string, []byte (variable length) inside the struct. package main import ( "bytes" "encoding/binary" "fmt" ) type Header struct { Magic [4]byte // fixed-size array is OK Version uint16 Flags uint16 Length uint32 } func main() { h := Header{ Magic: [4]byte{'D', 'A', 'T', 'A'}, Version: 1, Flags: 0, Length: 1024, } var buf bytes.Buffer // Write header in Big Endian _ = binary.Write(&buf, binary.BigEndian, h) // Read it back var h2 Header _ = binary.Read(&buf, binary.BigEndian, &h2) fmt.Println(string(h2.Magic[:]), h2.Version, h2.Length) } ===== 6. Typical patterns (store-able recipes) ===== ==== 6.1 Designing a binary message ==== A common simple message layout: 4 bytes: magic 2 bytes: version 4 bytes: payload length N bytes: payload Why: Fixed header makes parsing easy. Length tells you how many bytes to read next. ==== 6.2 Writing variable-length data (strings / []byte) ==== You usually do: Write length (fixed-size integer) Write raw bytes func WriteBytes(w *bytes.Buffer, p []byte) error { if err := binary.Write(w, binary.BigEndian, uint32(len(p))); err != nil { return err } _, err := w.Write(p) return err } length prefix /leŋkθ ˈpriːfɪks/ = tiền tố độ dài ==== 6.3 Reading variable-length data ==== func ReadBytes(r *bytes.Reader) ([]byte, error) { var n uint32 if err := binary.Read(r, binary.BigEndian, &n); err != nil { return nil, err } p := make([]byte, n) _, err := r.Read(p) return p, err } Tip: for real streams, prefer io.ReadFull to ensure you read exactly n bytes. ===== 7. Notes & pitfalls ===== Structs may include padding due to alignment; that can break “expected layout” across compilers/architectures. Safer: manually write each field in order. binary.Read/Write can be slower than manual PutUintXX for hot paths. Always define your protocol endianness explicitly (don’t rely on machine). padding /ˈpædɪŋ/ = byte đệm alignment /əˈlaɪnmənt/ = căn hàng bộ nhớ explicitly /ɪkˈsplɪsɪtli/ = một cách rõ ràng ===== 8. Quick map: “what is what for” ===== binary.BigEndian / binary.LittleEndian — choose byte order PutUint16/32/64 + Uint16/32/64 — fast byte-slice number encode/decode binary.Read / binary.Write — read/write fixed-size values (or structs) from/to streams Use encoding/gob when: Go-to-Go object serialization (not a fixed binary protocol) Use encoding/json when: human-readable, interoperable text format