Specification là nguồn sự thật duy nhất. Không bao giờ sửa spec để chiều một implementation.
Mọi thứ khác đều nằm phía sau nó.
Thứ tự Cyclone đi theo
RFC
Compiler
SDK
Application
Không phải thế này
SDK
Protocol
Một protocol bị nắn theo bất kỳ thứ gì thư viện đầu tiên của nó tình cờ làm.
Phép thử áp cho từng câu của specification:
một người viết encoder bằng assembly trên vi điều khiển 8-bit có làm theo được không?
Nếu không, câu đó đang mô tả một implementation, và nó không thuộc về spec.
Định nghĩa
Cyclone là gì?
Cyclone là một protocol.
Cyclone không phải
× RPC
× Networking
× Runtime
× Serialization Library
Cyclone chỉ quy định
Model
Bytes
Model
Transport, retransmission, thứ tự gói, matchmaking, mã hoá, nén, checksum —
tất cả đều xây phía trên Cyclone. Specification dừng lại đúng ở chỗ
byte được hình thành.
Triết lý
Mười nguyên tắc, hai tầng.
Bảy nguyên tắc là điều kiện tuân thủ. Ba nguyên tắc là lựa chọn của Reference Implementation.
Trộn lẫn hai thứ đó chính là cách một protocol âm thầm biến thành một framework.
Nguyên tắc của Specification
Vi phạm một trong số này thì không phải Cyclone.
01Specification First
Specification là nguồn sự thật duy nhất. Không bao giờ sửa spec để chiều một implementation.
02Protocol over Framework
Cyclone định nghĩa dữ liệu hiện ra trên wire như thế nào — không định nghĩa API, không định nghĩa runtime.
03Minimal Wire Format
Gửi dữ liệu, không bao giờ gửi mô tả dữ liệu. Không tên field, không type info, không tag, không padding.
04Deterministic by Design
Một giá trị, đúng một chuỗi byte. Không được tồn tại hai cách encode hợp lệ cho cùng một dữ liệu.
05Language Neutral
Không ngôn ngữ nào là ngôn ngữ chính. Không chi tiết nào trong format ưu ái một type system.
06Implementation Independent
Không quy định parser, macro hay generator nào. Spec mô tả byte rồi dừng ở đó.
07Implementation Replaceable
Reference Implementation không phải chuẩn. Thay toàn bộ nó, wire format vẫn không đổi.
Nguyên tắc của Reference Implementation
Là lựa chọn thiết kế, không phải điều kiện tuân thủ. Một implementation khác được phép chọn
khác, miễn byte sinh ra không đổi.
08User Owns the Model
Không bắt buộc tầng DTO, không base class — RI đọc những field bạn đánh dấu và bỏ qua phần còn lại.
09Compile-time over Runtime
Thứ gì xác định được lúc build thì xong lúc build.
10Zero Runtime Reflection
Wire format cho phép điều đó; spec không bắt buộc. AOT và bare-metal vẫn khả thi.
Quy tắc quyết định, gồm hai câu hỏi. Một: phát biểu này có quan sát được từ
byte stream không? Không → nó thuộc về Reference Implementation và không được đưa vào spec.
Hai: nó có vi phạm một trong bảy nguyên tắc trên không? Có → không thuộc Cyclone.
Đánh đổi
Những gì Cyclone cố ý không làm
Mỗi điều dưới đây đều là cái giá trực tiếp của một đảm bảo ở trên. Không điều nào nằm trong roadmap.
✕
Không có schema evolution
Vị trí là định danh field duy nhất, nên thêm, bớt hay đổi thứ tự một field đều là
breaking change. v1 không có thương lượng phiên bản, không có migration cho field.
✕
Không backward / forward compatibility
Không có unknown-field skipping, không có default, nên client cũ không đọc được message
của server mới. Hai đầu deploy cùng nhau, hoặc không deploy.
✕
Không metadata, không tự mô tả
Stream không mang tên field, kiểu hay tag — bên nhận bắt buộc phải biết trước thứ tự và
kiểu của từng field. Hệ quả nguy hiểm: decode sai định nghĩa thường
không báo lỗi gì cả. Nó sinh ra giá trị sai, im lặng.
✕
Không có IDL bắt buộc
Nguồn sự thật là Schema, nhưng spec không quy định bạn phải viết nó ra
bằng cách nào — không IDL bắt buộc, không file .cyclone bắt buộc. Reference
Implementation diễn đạt Schema bằng annotation ngay trên type có sẵn của bạn, chỉ trích ra
ba thứ: tên type, các field, và Cyclone type của từng field. Đổi lại: không có một tài liệu
schema độc lập để đưa cho bên thứ ba — hai phía tự lo việc rút ra cùng một Schema.
✕
Không Optional, không nullable, không default
Mọi field đều mang giá trị. v1 không có cách encode field vắng mặt — và chính vì vậy
không có chỗ nào mơ hồ về việc sự vắng mặt sẽ được ghi ra sao.
✕
Không transport, không framing, không crypto
Cyclone sinh ra một chuỗi byte rồi dừng. Ranh giới message, retransmission, thứ tự gói,
mã hoá và nén đều là phần bạn tự xây phía trên.
Nếu có điều nào không chấp nhận được với bài toán của bạn, một format có tag ID và optional field
sẽ phục vụ tốt hơn. Đó là lựa chọn đúng, không phải thất bại của Cyclone.
Specification
Specification
Ba tài liệu. Mỗi tài liệu trả lời đúng một câu hỏi.
Số lượng của [1,2,3] là 3, không phải 12. Mảng con rỗng là số lượng 0 và không byte nào thêm.
Enum
Luôn là UInt32. Bốn byte, bất kể nó khai báo ít thành viên tới đâu.
enum PlayerState { Idle=0, Walk=1, Run=2, Jump=3 }
Idle → [00 00 00 00]
Walk → [01 00 00 00]
Jump → [03 00 00 00]
99 → không hợp lệ — ngoài tập đã định nghĩa
Đây là quyết định của protocol, không phải tối ưu lưu trữ. Không có gì để hai đầu thương lượng.
Một message đầy đủ, từng byte một
Ví dụ từ RFC-0002 §15 — Model lồng, string, số nguyên, bool — encode ra 31 byte.
Rê chuột lên một field để làm sáng những byte nó sở hữu, hoặc rê lên một byte để tìm chủ của nó.
Không có gì ngăn cách các field. Không length prefix cho Model lồng, không dấu phân cách giữa
position và health, không terminator ở cuối. Decoder biết đang đọc field
nào thuần tuý nhờ con trỏ đã đi được bao xa — và đó chính là lý do
đổi thứ tự field là breaking change, còn đổi tên field thì miễn phí.
RFC-0003
Test vector
Một test vector là một cặp (giá trị logic, chuỗi byte). Không bao giờ là cặp
(lời gọi hàm, giá trị trả về) — một vector không nêu tên method nào, ngôn ngữ nào, API nào.
Input→Byte mong đợi
Primitive
ID
Input
Byte mong đợi
P-002
bool true
01
P-011
u8 255
FF
P-021
u16 300
2C 01
P-032
u32 0x12345678
78 56 34 12
P-035
i32 −2147483648
00 00 00 80
P-042
u64 2⁶⁴−1
FF FF FF FF FF FF FF FF
F-005
f32 0x4048F5C3 · 3.14
C3 F5 48 40
F-002
f32 0x80000000 · −0.0
00 00 00 80
F-031
f32 0x7FC00001 · NaN
01 00 C0 7F
S-004
String “中”
03 00 00 00 E4 B8 AD
S-005
String “Xin chào”
09 00 00 00 58 69 6E 20 63 68 C3 A0 6F
P-032 bắt lỗi endianness: một implementation big-endian sẽ ghi 12 34 56 78 và trượt ở đây.
F-031 bắt lỗi canonical hoá NaN — cho ra 00 00 C0 7F nghĩa là dữ liệu người dùng đã bị sửa.
Model
ID
Input
Byte mong đợi
T-001
Item{ id=42, name="Sword" }
2A 00 00 00 05 00 00 00 53 77 6F 72 64
T-002
Empty{}
— 0 byte
T-003
Vector3{1.5, 2.5, 3.5}
00 00 C0 3F 00 00 20 40 00 00 60 40
T-004
Body{ pos=Vector3{1.5,2.5,3.5}, hp=100 }
00 00 C0 3F 00 00 20 40 00 00 60 40 64 00 00 00
T-010
Mixed{ a=1, b=2, c=3 }
01 02 00 00 00 03
T-011
Order{ z=1, a=2 }
01 02
T-010 bắt lỗi chép layout bộ nhớ (12 byte) và lỗi compiler sắp xếp lại field.
T-011 bắt những implementation sắp field theo thứ tự chữ cái “cho ổn định” — 02 01 là bug.
Array
ID
Input
Byte mong đợi
A-001
Array<u32> []
00 00 00 00
A-002
Array<u32> [1,2,3]
03 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00
A-003
Array<String> ["a","bc"]
02 00 00 00 01 00 00 00 61 02 00 00 00 62 63
A-004
Array<Array<u8>> [[1,2],[]]
02 00 00 00 02 00 00 00 01 02 00 00 00 00
A-005
Array<bool> [true,false,true]
03 00 00 00 01 00 01
A-002 bắt lỗi nhầm số lượng với độ dài byte: số lượng đúng là 3, không phải 12.
Enum
ID
Input
Byte mong đợi
E-001
PlayerState::Idle
00 00 00 00
E-002
PlayerState::Walk
01 00 00 00
E-003
PlayerState::Jump
03 00 00 00
E-010
Kích thước encode của mọi enum
luôn 4 byte
E-010 bắt những implementation “tối ưu” một enum bốn thành viên xuống còn một byte.
Stream không hợp lệ
ID
Byte đầu vào
Kết quả mong đợi
N-001
bool · 02
từ chối · không phải 00/01
N-010
u32 · 00 00 00
từ chối · EOF bất ngờ
N-020
String · 02 00 00 00 FF FE
từ chối · UTF-8 không hợp lệ
N-021
String · 03 00 00 00 ED A0 80
từ chối · surrogate bị encode
N-023
String · 64 00 00 00 41 6C 69 63 65
từ chối · độ dài 100, còn 5 byte
N-030
Array<u32> · 03 00 00 00 01 00 00 00
từ chối · số lượng 3, có một phần tử
N-031
Array<u32> · FF FF FF FF
từ chối · trước khi cấp phát
N-040
enum{0,1} · 63 00 00 00
từ chối · giá trị 99 không định nghĩa
Từ chối là MUST; tên lỗi chỉ là SHOULD. N-023 và N-031 kiểm đúng một thứ:
độ dài có được kiểm tra trước khi cấp phát không? Pass N-031 bằng cách crash rồi bắt
exception thì không tính là pass.
Bộ đầy đủ — gồm số nguyên 8 byte, f64, kiểm tra round-trip và kiểm tra chéo giữa các
implementation — nằm trong
RFC-0003.
Conformance
Cyclone Compatible
Một implementation là Cyclone Compatible khi và chỉ khi nó pass
100% test vector.
✓ RFC-0002
✓ Test Vector
✓ Output xác định
Không có “tương thích một phần”. Không có 98%. Một vector trượt nghĩa là tồn tại một dữ liệu mà
implementation này và một implementation khác bất đồng — im lặng, trên production.
Một implementation chậm gấp 10 lần nhưng pass 100% vector là Cyclone Compatible.
Implementation nhanh nhất thế giới mà trượt một vector thì không.