AuthenticationServer
AuthenticationServer nodeای منطقی در انتهای chain برای مدیریت دیتابیس کاربران WaterWall است. این node socket باز نمیکند و مستقیماً I/O شبکه انجام نمیدهد؛ در عوض، یک دیتابیس در حافظه از نوع users_t نگه میدارد، آن را در قالب JSON بارگذاری و ذخیره میکند و به درخواستهای فریمبندیشده AuthenticationClient پاسخ میدهد.
ساعت و اولین استفاده محلی
هر پاسخ GetUsersPage باید هر دو فیلد canonical و دقیق server-time-ms و accounting-time-ms مثبت را داشته باشد؛
metadata نامعتبر refresh را بدون جایگزینی cache معتبر رد میکند و fallback به ساعت دیواری کلاینت وجود ندارد.
سرور یک بار C0 = max(wall-ms, 1) و monotonic M0 را ثبت میکند و با جمع saturating زمان سپریشده monotonic
ساعت حسابداری را پیش میبرد. اصلاح بعدی ساعت دیواری آن را reset نمیکند و anchor در فایل ذخیره نمیشود.
اولین upload یا download غیرصفر درون قفل write کاربر، نمونه تازه monotonic، origin محلی معتبر حتی در صفر و برآورد مثبت و تغییرناپذیر اولین استفاده برای گزارش را ثبت میکند. login، reservation، فراخوانی صفر بایت و rebase شروع مصرف نیستند. ثبت مشاهده، شمارنده و تصمیم انقضا در transaction با ترتیب user سپس stats انجام میشود؛ نبود خروجی notification مانع مشاهده نیست. سن سپریشده حتی با مدت صفر حفظ میشود؛ ACK، reconnect، pull تکراری یا نبود فیلد canonical آن را پاک یا جوان نمیکنند. مشاهده زودتر کلاینت دیگر میتواند سن را بیشتر کند. origin واردشده در آینده تا رسیدن زمانش آینده میماند؛ مصرف محلی واقعی میتواند origin زودتری بسازد.
انقضای تاریخ مطلق با ساعت دیواری سرور و مدت پس از اولین استفاده با سن monotonic جدا محاسبه میشوند. تاریخ بدون تغییر deadline زودتر را حفظ میکند؛ ویرایش/پاککردن هر سیاست فقط جزء خودش را دوباره محاسبه میکند. deadline زودتر حاکم است؛ انقضای گذشته در monotonic صفر هرگز به unlimited تبدیل نمیشود. state زمان/مشاهده/ACK و reservation با ID پایدار، حتی هنگام تغییر credential همان ID، منتقل میشوند؛ policy و timestamp مرجع جدید حفظ میشوند. کپی عمومی و JSON این state محلی را ندارند. تبدیل بین ماشینها برآوردی با خطای تأخیر شبکه/serialization و drift است. رفتار suspend ساعت runtime عوض نمیشود؛ CLOCK_MONOTONIC لینوکس زمان تعلیق میزبان را حساب نمیکند.
جایگاه رایج
AuthenticationClient -> AuthenticationServer
این node در انتهای chain قرار میگیرد و نباید next داشته باشد.
این نود چه میکند؟
- کاربران را از
db-pathدر یک جدول در حافظهusers_tبارگذاری میکند. - اگر فایل اصلی دیتابیس JSON بارگذاری نشود، سراغ
db-path.backupمیرود. - پس از بازیابی موفق از backup، فایل اصلی دیتابیس را دوباره مینویسد.
- دیتابیس فعلی در حافظه را بهصورت دورهای ذخیره میکند.
db-path.backupرا قبل از نوشتنdb-pathدر ذخیرهسازیهای عادی مینویسد.- میتواند snapshotهای تاریخی backup را بهصورت ساعتی، روزانه یا هفتگی بنویسد.
- پیامهای درخواست فریمبندیشده را از
AuthenticationClientمیپذیرد. - از چند فریم درخواست در یک پیام بیرونی پشتیبانی میکند.
- هر فریم درخواست را به ماژول داخلی مناسب برای احراز هویت یا مدیریت کاربر میفرستد.
- برای هر پیام پردازششده، یک پیام پاسخ ترکیبی شامل همه فریمهای پاسخ را در جهت downstream میفرستد.
AuthenticationServer مرجع اصلی اطلاعات کاربران است. nodeهایی که ترافیک کاربران را سرویس میدهند باید از طریق AuthenticationClient با آن ارتباط بگیرند و نباید فریمهای پروتکل سرور را مستقیم بفرستند.
نمونه تنظیم
{
"name": "auth-db",
"type": "AuthenticationServer",
"settings": {
"db-path": "users.json",
"file-save-rate-ms": 10000,
"normal-backups": "daily",
"normal-backups-path": "backups/",
"normal-backups-count-limit": 10,
"session-idle-timeout-ms": 600000,
"verbose": false,
"auth-clients": [
{
"name": "edge-1",
"secret": "long-random-secret",
"allow-stats-push": true,
"allow-user-pull": true,
"allow-user-write": false,
"session-idle-timeout-ms": 600000
}
]
}
}
فیلدهای اجباری
فیلدهای سطح اصلی:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام انتخابی node که باید در config یکتا باشد. |
type | string | باید دقیقاً "AuthenticationServer" باشد. |
settings | object | تنظیمات دیتابیس، ذخیرهسازی، backup، session و auth client. |
فیلدهای اجباری داخل settings:
| فیلد | نوع | توضیح |
|---|---|---|
db-path | string | مسیر فایل دیتابیس JSON کاربران. |
file-save-rate-ms | عدد مثبت | بازه ذخیره دورهای به میلیثانیه. |
auth-clients | آرایه غیرخالی | کلاینتهای control-plane که اجازه دارند در این سرور احراز هویت شوند. |
AuthenticationServer configهایی را که در آنها next تعریف شده باشد نمیپذیرد.
تنظیمات اختیاری
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
session-idle-timeout-ms | عدد مثبت | 600000 | timeout پیشفرض بیکار ماندن session، بر حسب میلیثانیه. |
verbose | boolean | false | لاگهای debug مربوط به چرخهٔ line، dispatch، رد مجوزها و صف/ارسال پاسخ را فعال میکند. |
normal-backups | string | غیرفعال | حالت backup تاریخی: "hourly"، "daily" یا "weekly". |
normal-backups-path | string | تنظیم نشده | دایرکتوری snapshotهای تاریخی backup. اگر normal-backups تنظیم شود، این گزینه اجباری است. |
normal-backups-count-limit | عدد مثبت | 10 | حداکثر فایلهای backup تاریخی که برای این دیتابیس و حالت backup نگه داشته میشوند. |
normal-backups و normal-backups-path باید با هم تنظیم شوند. اگر بدون فعال کردن backupهای عادی، normal-backups-count-limit را تنظیم کنید، راهاندازی با خطا متوقف میشود.
normal-backups-path میتواند مسیر نسبی یا مطلق باشد. اگر دایرکتوری وجود نداشته باشد سرور آن را میسازد؛ اما اگر مسیر وجود داشته باشد و دایرکتوری نباشد، راهاندازی شکست میخورد.
گزینههای file-save-rate-ms، session-idle-timeout-ms در سطح سرور و هر کلاینت، و
normal-backups-count-limit فقط عدد صحیح JSON از 1 تا 2147483647 (INT_MAX) میپذیرند.
file-save-rate-ms اجباری است. پیشفرض یا timeout ارثرسیدهٔ هر کلاینت فقط وقتی اعمال میشود که گزینه وجود نداشته باشد.
صفر، مقدار منفی یا اعشاری، نگارش دارای ممیز یا توان، رشته، null و مقدار خارج از بازه پیش از استفاده در timer یا نگهداری backup، راهاندازی را متوقف میکنند.
verbose و مجوزهای allow-stats-push، allow-user-pull و allow-user-write در هر producer
فقط Boolean واقعی JSON میپذیرند و در نبود فیلد false هستند. رشته، عدد، null و سایر نوعها یا تکرار این
فیلدها در همان object راهاندازی را رد میکند. پیام خطا نام فیلد و برای مجوز producer، index آرایه را مشخص میکند.
Auth Clients
auth-clients مشخصات ورود control-plane را برای instanceهای AuthenticationClient تعریف میکند. این موارد با کاربران عادی ترافیک در دیتابیس کاربران فرق دارند.
هر ورودی باید این فیلدها را داشته باشد:
| فیلد | نوع | توضیح |
|---|---|---|
name | non-empty string | نام کلاینت احراز هویت. |
secret | non-empty string | secret مشترک با همان کلاینت احراز هویت. |
فیلدهای اختیاری هر کلاینت:
| فیلد | نوع | پیشفرض | توضیح |
|---|---|---|---|
allow-stats-push | boolean | false | به ماژولهای همگامسازی مصرف ترافیک اجازه اجرا میدهد. |
allow-user-pull | boolean | false | به ماژولهای جستوجوی کاربر و دریافت کامل جدول اجازه اجرا میدهد. |
allow-user-write | boolean | false | ماژولهای ایجاد/بهروزرسانی کاربر را مجاز میکند. |
session-idle-timeout-ms | عدد مثبت | timeout سطح اصلی | timeout پیشفرض sessionهای ساختهشده برای این کلاینت را جایگزین میکند. |
این دسترسیها به session مربوطاند، نه به کاربران:
| Permission | مجاز میکند |
|---|---|
allow-user-pull | GetUsersPage، user lookup با password، SHA-224 یا SHA-256. |
allow-stats-push | PushUserStats. |
allow-user-write | AddNewUser و UpdateUser. |
Authenticate برای همه قابل استفاده است. تمام ماژولهای دیگر به یک session token معتبر نیاز دارند.
لاگ ممیزی مدیریت
نبودن audit-log این قابلیت را غیرفعال میکند. برای فعالکردن، این object را در
settings قرار دهید:
"audit-log": {
"path": "logs/auth-admin.jsonl",
"max-file-size": 16777216,
"max-files": 7
}
path اجباری و نام دقیق فایل فعال است؛ مسیر نسبی از working directory فرایند
محاسبه میشود. دایرکتوری والد باید پیش از startup موجود باشد. max-file-size
حد صحیح بایت، با پیشفرض 16777216 (16 MiB) و بازهٔ 16384 تا 1073741824 است.
max-files از ۲ تا ۳۲ و پیشفرض ۷ است و فایل فعال را نیز حساب میکند. کلید
ناشناخته، تکراری و type نامعتبر رد میشوند. log level وجود ندارد و فیلتر سطح
لاگر معمولی، رویدادهای ممیزی را حذف نمیکند.
پیش از نوشتن رکوردی که فایل را از سقف عبور دهد، rotation انجام میشود. آرشیوها
از path.1 (جدیدترین) تا path.N-1 هستند؛ قدیمیترین هنگام نیاز حذف میشود.
فایل کامل موجود در startup ادامه پیدا میکند و truncate نمیشود. فایل فعال
غیرخالی که با newline تمام نشده باشد رد میشود؛ پیش از restart آن را بررسی و
ترمیم کنید. فایل فعال، آرشیوها و sidecar پایدار path.lock متعلق به همین writer
هستند. مسیر اختصاصی و جدا از دیتابیس، backup و لاگ معمولی، داخل دایرکتوری تحت
کنترل اپراتور انتخاب کنید. هنگام فعالبودن backup معمولی، نام فعال مطابق
authenticationserver-*.json در normal-backups-path رزرو است؛ aliasهای همان
دایرکتوری نیز رد میشوند. نام متمایزی مانند auth-admin.jsonl میتواند در همان
دایرکتوری قرار گیرد. هنگام اجرا lock را حذف و فایلها را با ابزار بیرونی
rename یا rotate نکنید. writer فایل عادی بدون link alias میخواهد، permission
خصوصی اعمال میکند و writer دوم را نمیپذیرد. در Unix مجوز 0600 و در Windows
یک ACL حفاظتشده برای کاربر فرایند و SYSTEM اعمال میشود. اگر لاگ فعال نتواند
فایلها را باز یا اعتبارسنجی کند، startup شکست میخورد.
اعتبارسنجی مسیر سقف فعلی MAX_PATH را برای نام تنظیمشده، پسوندهای آرشیو و lock
و نام parent پس از resolve حفظ میکند. مسیر یا طول پشتیبانینشده، شکست resolve
دایرکتوری parent و همپوشانی واقعی با فایل حفاظتشده، diagnosticهای جدا دارند.
شکست resolve نیز startup را رد میکند.
تلاشهای موفق و ردشدهٔ create-user، update-user و change-user-password
که به handler ثبتشده برسند، برای commit ثبت و برای rejection نمونهبرداری میشوند. نتیجهٔ commit در AddNewUser و UpdateUser
پروتکل تولیدکننده نیز ثبت میشود؛ rejection، از جمله رد مجوز تولیدکنندهٔ احرازشده، تابع همان نمونهبرداری است.
خواندنها، احراز هویت، batch ترافیک و رد درخواست در gateway پیش از dispatch
رکورد مدیریتی ایجاد نمیکنند. این قابلیت تاریخچهٔ مدیریت حساب است، نه لاگ
کامل دسترسی و امنیت.
هر خط JSON با UTF-8 فقط این metadata محدود را دارد:
| فیلد | معنا |
|---|---|
timestamp_ms | میلیثانیهٔ Unix با ساعت دیواری UTC، بهشکل رشتهٔ دهدهی؛ اصلاح ساعت میتواند آن را عقب ببرد. |
server_instance, sequence | هویت همین نمونهٔ سرور و ترتیب پذیرش رکورد؛ sequence رشتهٔ دهدهی است. برای تشخیص restart و ترتیب استفاده شوند. |
config, node | نام منطقی config و node مقصد. |
source, actor | api و نام client احرازشدهٔ API، یا producer و نام تولیدکننده؛ summary زمان drain منبع server و actor تهی دارد. هویت نامعلوم null است. |
gateway_config, gateway_node | هویت APIServer برای API؛ در درخواست تولیدکننده null. نام client بهتنهایی لزوماً یکتای سراسری نیست. |
operation, user_id | نام ثابت operation و ID canonical دهدهی؛ اگر parsing ID را مشخص نکرده باشد null. |
outcome, error | committed با خطای null، یا rejected با کد خطای ثابت. |
config_revision | revision دقیق commit بهشکل رشتهٔ دهدهی؛ برای رد درخواست null. |
fields | نام leafهای تنظیمات انتخابشده، بدون مقدار؛ ساخت/تغییر رمز از markerهای account و/یا credentials استفاده میکند. ویرایش با مقدار قبلی نیز میتواند نام فیلد داشته باشد. |
node_suppressed_rejections | شمار دقیق دهدهی ردهای عمداً حذفشده در کل node؛ متعلق به actor یا error همان نمونه نیست. فقط در رکوردهای rejection میآید. |
identity_truncated | آیا label هویت actor/gateway از سقف کپی محدود بیشتر بوده است. نامهای پیکربندیشدهٔ مقصد/تولیدکننده هنگام فعالبودن ممیزی اعتبارسنجی میشوند. |
هر دو منبع spelling عمومی API مانند enabled، notes، limit.traffic.u،
wireguard_allowed_ips و time.expire_at_ms را بهکار میبرند. رمز، credential
مشتقشده، secret تولیدکننده، token، بدنهٔ درخواست و مقدار فیلد ویرایششده هیچگاه
وارد رکورد نمیشوند. رویداد موفق همان لحظهٔ commit revision و پیش از ارسال
پاسخ صف میشود؛ گمشدن پاسخ آن را به rejection تبدیل نمیکند. AddNewUser
تولیدکننده رفتار موجود ذخیره و rollback را حفظ میکند؛ شکست ذخیره همراه rollback
موفق، نتیجهٔ ردشده است.
صف ثابت ۲۵۶ رکورد، عملیات حساب را از نوشتن فایل جدا میکند. mutex کوتاه فقط کپی metadata محدود را حفاظت میکند؛ ساخت JSON، I/O، rotation و diagnostic در یک thread مستقل انجام میشوند. پرشدن صف یا خطای فایل حساب را rollback نمیکند و ACK موفق را به شکست تبدیل نمیکند. صف پر، رویداد commit جدید را حذف و سلامت را نامناسب میکند. خطای I/O writer همین نمونه را fault میکند تا بعد از بایتهای نامطمئن/ناقص رکورد دیگری نوشته نشود؛ رکوردهای صفشده و بعدی dropped حساب میشوند. پس از اصلاح علت، restart لازم است. shutdown عادی رکوردهای پذیرفتهشده را پیش از آزادکردن writer تخلیه میکند؛ writer faultشده رکوردهای ازدسترفته را بازیابی نمیکند.
ردهای audit در کل هر node و بین API/producer حداکثر یک نمونه در پنج ثانیهٔ monotonic و فقط زیر occupancy برابر 16 از 256 پذیرفته میشوند؛ زمان idle burst جمع نمیکند و commit محدودسازی نمونه ندارد. حذف عمدی در counter اشباعشوندهٔ suppressed_rejections بهشکل رشتهٔ دهدهی ثبت میشود و health را خراب نمیکند. نمونهٔ بعدی node_suppressed_rejections را برای کل node حمل میکند، نه actor/error آن نمونه. drain ممکن است یک summary با source=server، operation=rejection-summary، outcome=rejected و error=suppressed-rejections و actor/gateway/user/revision تهی و fields خالی بنویسد. شکست writer پیش از sampling بررسی میشود؛ loss واقعی همچنان dropped_records و health ناسالم چسبنده دارد. diagnostic روتین Ping/AddNewUser فقط verbose/LOGD است؛ خطای واقعی save/file/resource باقی میماند.
status.audit همیشه وجود دارد و شامل enabled، healthy، queued_records،
written_records، dropped_records، suppressed_rejections، write_failures و last_error است.
شمارندهها رشتههای unsigned دهدهی و متعلق به عمر همین node هستند.
queued_records رکورد در حال نوشتن را حساب نمیکند؛ written_records تعداد
append و flush موفق است. شمارندهها مستقل نمونهبرداری میشوند و معادلهٔ
حسابداری snapshotشده نیستند. پس از حذف رکورد یا خطای I/O، healthy false
میماند. last_error برابر null یا یکی از queue_full، sequence_exhausted،
open، write، flush، rotate، close، invalid-file است. status مسیر فایل،
متن خطای سیستم یا محتوای رکورد را افشا نمیکند. حالت غیرفعال healthy، شمارندهٔ
صفر و خطای null دارد. لاگر diagnostic معمولی نیز خطای startup/runtime را
گزارش میکند.
صفشدن به معنی نوشتهشدن نیست و flush تضمین دوام پس از crash نیست. لاگ با دیتابیس کاربران transaction اتمیک مشترک ندارد، برای هر رکورد disk-sync تضمین نمیکند، replay/recovery یا تشخیص دستکاری ارائه نمیدهد. موفقیت API همچنان تعهد حافظهای موجود است؛ رکورد ممیزی ثابت نمیکند ذخیرهٔ دیتابیس یا refresh کلاینت کامل شده است.
فرمت دیتابیس
فرمت پیشنهادی روی دیسک، یک object دارای آرایه users است:
{
"users": [
{
"id": 1001,
"name": "alice",
"password": "alice:alice-secret",
"email": "alice@example.com",
"wireguard-allowed-ips": "10.44.0.23/32",
"enabled": true,
"limit": {
"traffic": {
"up": 1073741824,
"down": 1073741824,
"total": 2147483648
},
"bandwidth": {
"up": 1048576,
"down": 1048576
},
"ips": 4,
"devices": 2,
"connections-in": 16,
"connections-out": 16
},
"time": {
"created-at-ms": 1735689600000,
"expire-at-ms": 1767225600000
},
"stats": {
"traffic": {
"up": 0,
"down": 0
},
"connections-in": 0,
"connections-out": 0
}
}
]
}
Loader چند شکل مختلف از ورودی را میپذیرد:
| Layout | توضیح |
|---|---|
null، آرایه خالی، یا object خالی | بهعنوان جدول کاربران خالی پذیرفته میشود. |
[user, ...] | آرایهای از objectهای کاربر. |
{"users": [user, ...]} | ساختار پیشنهادی. |
{"alice": user, "bob": user} | یک object map. اگر کاربر name غیرخالی نداشته باشد، کلید فقط بهعنوان راهنمای نام کاربری استفاده میشود. |
| یک object مستقل کاربر | در صورتی پذیرفته میشود که id یا user-id و همچنین password یا pass داشته باشد. |
وجود مقدار string در password یا pass یک رکورد مستقل را مشخص میکند؛
ورودیهای object با نام password یا pass همچنان کاربرهای عادی در map هستند.
هنگام اعتبارسنجی credential، فیلد موجود password بر pass اولویت دارد، حتی
اگر مقدار انتخابشده نامعتبر باشد. wrapper دارای users نیز بر layoutهای دیگر
اولویت دارد. import پیش از اعتبارسنجی حداکثر ۶۴ رکورد بیش از تعداد فعلی رزرو
میکند و با پذیرفتهشدن رکوردها گسترش مییابد. شکست append محتوای قبلی و indexها
را برمیگرداند؛ لازم نیست ظرفیت تخصیصیافته کاهش یابد.
Saver همیشه دیتابیس را با ساختار {"users": [...]} بازنویسی میکند.
پیش از import، reader طول واقعی فایل را نگه میدارد و کل سند JSON را بررسی میکند. بایت اضافی غیر از whitespace، چند سند چسبیده، NUL خام یا decodeشده، UTF-8 نامعتبر و کلید تکراری پس از decode باعث رد فایل میشوند. whitespace معمول JSON در پایان مجاز است. primary نامعتبر مسیر عادی بازیابی backup را طی میکند. محدودیت عمق فعلی cJSON حفظ میشود؛ سقف بایت یا تعداد مقدار envelopeهای API به دیتابیس تحمیل نمیشود.
Object کاربر
هر object کاربر باید این فیلدها را داشته باشد:
| فیلد | نوع | توضیح |
|---|---|---|
id یا user-id | unsigned integer غیرصفر | شناسه منطقی و ماندگار کاربر که باید یکتا و ثابت باشد. |
password یا pass | string | کلید جستوجوی password که indexهای مشتقشده بر اساس آن ساخته میشوند. |
فیلدهای اختیاری:
| فیلد | توضیح |
|---|---|
name | نام خوانای کاربر. نامهای غیرخالی باید یکتا باشند. |
email | اطلاعات ایمیل کاربر. |
notes | یادداشت آزاد مدیر سیستم. |
gid یا group-id | شناسه گروه. |
enabled یا enable | پیشفرض true؛ برای غیرفعال کردن کاربر false بگذارید. |
record-stat-interval-ms | فاصله ثبت آمار. |
wireguard-allowed-ips | یک CIDR داخلی و اختیاری WireGuard برای این کاربر، مانند 10.44.0.23/32 یا fd00::23/128. مقدار خالی، null یا نبودن این فیلد یعنی تنظیم نشده است. IPv4 و IPv6 پذیرفته میشوند؛ مقدار به شکل استاندارد network ذخیره میشود و محدوده آن نباید با کاربر دیگری همپوشانی داشته باشد. |
limit | محدودیتهای کاربر. مقادیر مفقود یا صفر به معنی نامحدود است. |
time | اطلاعات زمانی کاربر. |
stats | آمار runtime که معمولاً WaterWall آن را نگه میدارد. |
برای Socks5Server، کلید نهایی جستوجو را در password و با قالب username:password ذخیره کنید؛ برای مثال "alice:alice-secret".
Passwordها بهصورت plaintext در دیتابیس JSON ذخیره میشوند، زیرا اطلاعات exportشده کاربران فیلد password را هم در بر دارد. با permissionهای مناسب فایلسیستم از db-path، فایل db-path.backup و دایرکتوری backupهای عادی که حاوی اطلاعات محرمانهاند محافظت کنید.
کلیدهای جستوجوی مشتقشده از password مستقیماً در JSON ذخیره نمیشوند. هنگام بارگذاری کاربران، SHA-224، SHA-256، مشخصات UUID و public keyهای سازگار با WireGuard دوباره از password ساخته میشوند. هیچ دو کاربری نباید password/SHA-224، SHA-256، UUID یا public key مشتقشده WireGuard یکسان داشته باشند.
آدرس Allowed-IP باید syntax کامل و سختگیرانهٔ IPv4 نقطهای دهدهی یا IPv6، از جمله IPv4 تعبیهشدهٔ معتبر، داشته باشد. IPv4 کوتاهشده، octal یا hex، گروه IPv6 بلندتر از چهار رقم و متن اضافه رد میشوند. فقط فاصلهٔ اطراف token حذف میشود؛ فاصلهٔ داخلی نامعتبر است. مقدار دقیقاً یک CIDR است و فهرست جداشده با comma نیست. ماسککردن host bit و جلوگیری از همپوشانی محدودهها حفظ میشود.
wireguard-allowed-ips بخشی ماندگار از config کاربر است و از password مشتق نمیشود. این فیلد در JSON ذخیره و با GetUsersPage همگام میشود؛ همچنین اعتبارسنجی میشود تا محدودههای کاربران با یکدیگر همپوشانی نداشته باشند.
محدودیتها، زمان و آمار
limit میتواند داشته باشد:
| فیلد | توضیح |
|---|---|
traffic | محدودیت تعداد بایت برای up، down و total. نامهای مستعار u و d نیز پذیرفته میشوند. |
bandwidth | محدودیت بایت بر ثانیه برای up و down. نامهای مستعار u و d نیز پذیرفته میشوند. |
ips یا ip | حداکثر تعداد IP. |
devices | حداکثر تعداد دستگاه. |
connections-in یا cons-in | حداکثر تعداد اتصال ورودی. |
connections-out یا cons-out | حداکثر تعداد اتصال خروجی. |
time میتواند داشته باشد:
| فیلد |
|---|
created-at-ms یا created_at_ms |
first-usage-at-ms یا first_usage_at_ms |
expire-at-ms یا expires-at-ms |
expire-after-first-usage-ms یا expire-after-first-use-ms |
همین فیلدهای زمانی در سطح اصلی object کاربر نیز پذیرفته میشوند. همه زمانها تعداد میلیثانیه از Unix epoch هستند؛ بهجز expire-after-first-usage-ms که یک مدتزمان است.
stats میتواند داشته باشد:
| فیلد | توضیح |
|---|---|
traffic | شمارندههای بایت برای up و down. نامهای مستعار u و d نیز پذیرفته میشوند. |
speed | شمارندههای سرعت برای up و down. نامهای مستعار u و d نیز پذیرفته میشوند. |
ips یا ip | تعداد IP runtime. |
devices | تعداد دستگاهها در runtime. |
connections-in یا cons-in | تعداد اتصالهای ورودی در runtime. |
connections-out یا cons-out | تعداد اتصالهای خروجی در runtime. |
مقادیر عددی بزرگ را میتوان بهصورت رشته دهدهی خواند و نوشت.
ذخیرهسازی و بازیابی
ذخیره زیر write lock سرور انجام میشود: ابتدا دیتابیس پیشنهادی در فایل موقت خصوصی کنار primary نوشته و sync
میشود؛ سپس primary قبلاً commitشده بهعنوان db-path.backup آماده و اتمی منتشر میشود؛ در پایان primary
با فایل پیشنهادی اتمی جایگزین میشود. این جایگزینی نقطه commit است. شکست پیش از آن، افزودن کاربر را رد میکند
و حافظه rollback میشود؛ backup شامل افزودن ردشده نخواهد بود. شکست نگهداری تاریخچه، pruning یا directory sync
پس از جایگزینی، commit را به شکست API تبدیل نمیکند و فقط گزارش میشود.
اگر open خود فایل تأیید کند که primary وجود ندارد، جدول معتبر فعلی از حافظه مستقیماً منتشر میشود و recovery backup موجود دستنخورده میماند؛ وجود backup الزامی نیست. ذخیرهٔ عادی بعدی rotation را از سر میگیرد. فایل خالی یا شکست open، read یا close بهمعنای نبودن فایل محسوب نمیشود.
تمام فایلهای primary، recovery، تاریخچه و موقت پیش از نخستین بایت محرمانه خصوصی هستند: روی POSIX با ایجاد
انحصاری 0600 و روی Windows با DACL محافظتشده برای کاربر process و SYSTEM. هنگام startup مجوز primary و
recovery موجود محدود میشود؛ نبودن backup مجاز است. حتی با primary معتبر، مجوز backup موجود باید محدود شود،
بدون خواندن یا parse محتوای آن. بنابراین backup خالی یا خراب مانع بارگذاری primary معتبر نیست، اما شکست
محدودسازی مجوز startup را بدون جایگزینی هیچیک از دو فایل متوقف میکند. اجرا با root پشتیبانی میشود: لازم نیست
مالک فایل موجود همان کاربر process باشد و محدودسازی مجوز نه مالکیت فایل را تغییر میدهد و نه دسترسی process را.
خطاهای عادی دسترسی و نوشتن فایلسیستم همچنان اعمال میشوند. write، flush، file sync و close بررسی میشوند. فایل موقت نام یکتا دارد، نامزد recovery نیست و cleanup فقط فایل موقت همان عملیات را پاک میکند.
پایان عادی process دیتابیس بارگذاریشده را ذخیره میکند؛ rollback راهاندازی
منابع ساختهشده را بدون ذخیرهٔ نهایی آزاد میکند، تا شکست دیرهنگام ساخت node
محتوای primary و recovery را بازنویسی نکند. slot هفتگی تاریخچه بر تقویم محلی
از دوشنبه استوار است و در تغییر ساعت تابستانی و مرز سال نیز ثابت میماند.
primary معتبر اولویت دارد. فقط نبودن فایل یا خرابی JSON/ساختار، مسیر بررسی backup را فعال میکند؛ خطای I/O، مجوز، منبع native یا ساخت candidate راهاندازی را متوقف میکند و هیچیک از دو فایل جایگزین نمیشوند. برای primary خراب، ابتدا backup کاملاً معتبر بررسی میشود و سپس بایتهای دقیق primary در sibling خصوصی و یکتای <db-path>.corrupt-<id> با ایجاد انحصاری و بدون overwrite حفظ میشوند. flush و close آرشیو باید پیش از بازیابی اتمی موفق باشند؛ شکست archive از جایگزینی جلوگیری میکند و شکست restore آرشیو و backup را نگه میدارد. آرشیوها متعلق به اپراتورند و نامزد recovery، pruning یا حذف خودکار نیستند. تاریخچه برای بازیابی خودکار استفاده نمیشود. backup از پیش موجود
اطلاعاتی برای اثبات زمان commit محتوایش ندارد. تضمین کلی در برابر خرابی filesystem یا قطع برق داده نمیشود.
retention تاریخچه snapshot تازهٔ موفق را بهعلاوهٔ جدیدترین نامهای لغوی دورههای مجاز دیگر تا limit نگه میدارد؛ عقبرفتن ساعت یا limit یک آن snapshot را حذف نمیکند. این ترتیب نام دوره است، نه زمان ایجاد. خطای حذف جدا از primary commit گزارش میشود؛ slot snapshot موفق حفظ میشود و pruning با snapshot دوره بعد تکرار میشود؛ فایل نامرتبط و corrupt archive کنار گذاشته میشوند.
Sessions و Revisionها
نامهای auth-clients باید یکتا باشند و processهای مستقل نامهای مستقل داشته باشند. session شامل token، مجوزها،
زمان بیکاری و اشاره به هویت تنظیمشده است. هر هویت یک checkpoint با epoch، آخرین sequence و SHA-256 آخرین
payload دارد؛ expiry یا جایگزینی session این checkpoint را پاک نمیکند. session و checkpoint روی دیسک ذخیره
نمیشوند. timeout پیشفرض 600000 ms است و worker صفر هر 60000 ms sessionهای بیکار را پاک میکند.
درخواست با token معتبر زمان فعالیت را تازه میکند؛ قفل state مانع حذف session در حین dispatch میشود.
config_revision و stats_revision از ۱ شروع میشوند و بهترتیب با تغییر تنظیمات و مصرف جدید افزایش مییابند.
point readها read lock مشترک میگیرند؛ احراز هویت، ذخیره و mutation زیر write lock و page read زیر read lock مشترک اجرا میشوند.
مدل همگامسازی
Authenticate اولیه name و secret میگیرد. پاسخ Session شامل ۱۲۸ بایت است: token شصتوچهاربایتی و سپس epoch
شصتوچهاربایتی؛ هر کدام نمایش hexadecimal سیودو بایت random امن سیستمعامل هستند. اتصال مجدد همان process،
epoch را نیز در Authenticate میفرستد. epoch گمشده یا جایگزینشده خطای accounting-continuity-lost میدهد.
ثبت تازه بدون epoch، checkpoint جدید ایجاد میکند و session قبلی آن هویت را نامعتبر میکند.
کلاینت یک batch دلتا را با بایتهای ثابت نگه میدارد. ترتیب reconnect عبارت است از authenticate/resume، تکرار
batch حلنشده، اعتبارسنجی ACK و سپس pull کامل. ID پایدار کاربر کلید مصرف است و تغییر password آن را عوض نمیکند.
با ACK معتبر، baseline فقط برای کاربران همان subset تا snapshot جلو میرود. pull شمارندههای محلی را با
فرمول G + (A - B) نصب میکند؛ ترافیک پس از snapshot از بین نمیرود. هنگام batch حلنشده، جایگزینی جدول ممنوع
است و درخواستهای pull/push مسدودشده برای اجرای بعدی ادغام میشوند.
تضمین حسابداری فقط قطع اتصال در زمان زندهبودن دو process را پوشش میدهد. اگر نتیجه batch نامطمئن باشد و epoch
از دست برود، کلاینت batch را حفظ میکند، وارد kAuthenticationClientStateContinuityError میشود و همگامسازی
خودکار را برای رسیدگی اپراتور متوقف میکند. اگر batch نامطمئن ندارد، ثبت تازه و pull مجاز است. بازیابی مصرف پس از
crash/restart ارائه نمیشود.
ACK فقط پذیرش در حافظه سرور است، نه تأیید ذخیره روی دیسک. ذخیره دورهای/نهایی دیتابیس checkpoint را ذخیره نمیکند. راهاندازی دوباره کلاینت ممکن است مصرف گزارشنشده را از بین ببرد؛ راهاندازی دوباره سرور ممکن است مصرف ACKشده پس از آخرین ذخیره موفق را از دست بدهد و checkpointها را پاک میکند. shutdown تضمین تحویل نهایی مصرف نیست. بازیابی مصرف پس از crash و عملیات remote reset وجود ندارد.
فریمبندی درخواست و پاسخ
بایتهای upstream به شکل stream buffer میشوند. envelope بیرونی درخواست چنین ساختاری دارد:
| Bytes | فیلد |
|---|---|
4 | Unsigned big-endian body size. |
64 | session token. |
| بقیه body | یک یا چند request frame. |
اندازه body شامل token شصتوچهاربایتی بهعلاوه payload فریم درخواست است. در درخواست احرازنشده Authenticate، همه بایتهای token میتوانند صفر باشند. درخواستهای دیگر به token معتبر نیاز دارند.
هر request frame:
| Bytes | فیلد |
|---|---|
1 | Request type. |
4 | Correlation ID. |
4 | Unsigned big-endian request data length. |
| variable | Request data bytes. |
ساختار envelope پاسخ:
| Bytes | فیلد |
|---|---|
4 | Unsigned big-endian body size. |
8 | Unsigned big-endian config_revision. |
8 | Unsigned big-endian stats_revision. |
| بقیه body | یک یا چند response frame. |
هر response frame:
| Bytes | فیلد |
|---|---|
1 | Response type. |
4 | Correlation ID کپیشده از request. |
4 | Unsigned big-endian response data length. |
| variable | Response data bytes. |
پیامی که session token معتبر دارد نباید شامل Authenticate باشد. envelope خراب، فریم ناقص، payload بزرگتر از حد مجاز یا بایتهای اضافهای که حتی یک header کامل درخواست را تشکیل نمیدهند، باعث بسته شدن امن اتصال منطقی میشوند.
در rollback ذخیرهٔ ناموفق AddNewUser، فقط slot آخرین افزودن منتشرنشده زیر مالکیت انحصاری پیوستهٔ
تراکنش پس گرفته میشود؛ تلاش مجدد از ظرفیت نگهداشتهشده استفاده میکند. حذف عادی همچنان slot پایدار را نگه
میدارد و اشارهگر حذفشده را نامعتبر میکند.
Allowed-IP از نوع IPv4-mapped IPv6 خانوادهٔ IPv6 را حفظ میکند: ::ffff:10.0.0.1/128 برای lookup و
تشخیص overlap با IPv4 10.0.0.1/32 متفاوت است و به آن تبدیل نمیشود.
خروجی کامل JSON در APIهای native مانند userToJson()، usersToJson() و usersUserToJson*() دارای
credential و مخصوص caller مورداعتماد است. caller مالک درخت برگشتی و مسئول حذف آن است. خروجی کل جدول،
زیر قفل read برای همهٔ حسابها تخصیص و کپی انجام میدهد؛ وضعیت عمومی باید از projectionهای redacted موجود استفاده کند.
انواع درخواست
| نوع | ماژول | Permission | داده request | response موفق |
|---|---|---|---|---|
1 | ping | valid session | literal ping | نوع 1، data pong |
2 | GetUserBySHA256Hex | allow-user-pull | 64 کاراکتر hex | نوع 2، user JSON |
3 | GetUserBySHA256Base64 | allow-user-pull | padded base64 SHA-256 digest | نوع 2، user JSON |
4 | GetUserBySHA256 | allow-user-pull | raw 32-byte SHA-256 digest | نوع 2، user JSON |
5 | GetUserByPassword | allow-user-pull | کلید جستجوی password plaintext | نوع 2، user JSON |
6 | AddNewUser | allow-user-write | یک user JSON object | نوع 0، user-added |
7 | UpdateUser | allow-user-write | patch تنظیمات با id پایدار | نوع 0، user-updated |
9 | GetUsersPage | allow-user-pull | checkpoint و cursor اختیاری | نوع 3، صفحه users به همراه metadata و validation |
10 | Authenticate | عمومی | JSON با name و secret | نوع 4، token 64 بایتی |
11 | PushUserStats | allow-stats-push | batch دلتا با epoch/sequence و ID | نوع 0، ACK دارای digest |
12 | GetUserBySHA224Hex | allow-user-pull | 56 کاراکتر hex | نوع 2، user JSON |
13 | GetUserBySHA224Base64 | allow-user-pull | padded base64 SHA-224 digest | نوع 2، user JSON |
14 | GetUserBySHA224 | allow-user-pull | raw 28-byte SHA-224 digest | نوع 2، user JSON |
انواع پاسخ:
| نوع | معنی |
|---|---|
0 | ok |
1 | pong |
2 | user |
3 | users database |
4 | session token |
255 | error |
برای نوع درخواست ناشناخته، پاسخ خطای unknown-request-type برگردانده میشود.
یادداشتهای ماژول
جستوجوی password و digest
همهٔ ماژولهای جستوجوی password و digest، پیش از کپی رشتههای کاربر، اندازهٔ کامل JSON فشرده را بررسی میکنند.
JSON باید در سقف payload پاسخ، یعنی ۱۶ MiB منهای header فریم ۹ بایتی، جا شود. رکورد حتی یک بایت بزرگتر
با response-too-large رد میشود و هرگز truncate نمیشود. encoding نامعتبر رشتهٔ ذخیرهشده خطای
user-json-serialize-failed دارد. قفل collection تا پایان کپی محدودشده نگه داشته میشود تا حذف همزمان
نتواند اشارهگر حساب انتخابشده را نامعتبر کند.
Authenticate
داده درخواست {"name":"edge-1","secret":"long-random-secret"} است؛ برای resume فیلد epoch نیز ارسال
میشود. envelope باید token صفر داشته باشد. پاسخ موفق نوع 4 و شامل token و epoch با طول مجموع ۱۲۸ بایت است.
احراز هویت در envelope دارای token معتبر با already-authenticated رد میشود. شکست resume خطای
accounting-continuity-lost میدهد. فقط عدم تطبیق name/secret خطای authentication-failed دارد.
پس از تطبیق معتبر، کمبود ظرفیت یا شکست تولید token/epoch خطای موقت session-unavailable میدهد؛ session،
epoch و checkpoint قبلی حفظ میشوند و کلاینت با backoff محدود دوباره متصل میشود، بدون از دست دادن cache
قابلاستفاده یا حسابداری معلق.
GetUsersPage
درخواست 9 با نام GetUsersPage (alias داخلی GetAllUsers) هر بار یک صفحه credential-bearing برمیگرداند؛
whole-table fallback وجود ندارد. درخواست نخست epoch، sequence و digest checkpoint تأییدشده کلاینت را دارد؛
sequence رشتهٔ "0" و digest رشتهٔ ۶۴ صفر در enrollment است. continuation علاوه بر آنها cursor و total دارد.
cursor شامل instance سرور ۱۶ بایتی، revision پیکربندی ۶۴ بیتی و position بعدی ۶۴ بیتی است، همگی در ۶۴ کاراکتر
hex کوچک. cursor مجوز دسترسی نیست؛ token فعلی و allow-user-pull در هر درخواست بررسی میشوند.
schema پاسخ نوع 3: instance، epoch، sequence، digest، config-revision، stats-revision،
server-time-ms، accounting-time-ms، total، position، count، cursor، complete، validated و
users. همه metadataهای unsigned64 رشتهٔ decimal دقیقاند. صفحه معمولی حداکثر ۱۲۸ user و هدف ۲۵۶ KiB پاسخ
کامل دارد. user نخستِ بزرگتر میتواند بهتنهایی تا سقف framing فعلی ۱۶ MiB منهای metadata/header جا شود؛
رکورد بزرگتر با user-too-large رد میشود، بدون truncate یا کپی کامل رشتهٔ خارج از حد. server snapshot کامل encoded را با سقف مشترک snapshot-max-bytes نگه میدارد. درخواست حداکثر ۱۰۲۴ بایت، عمق دو و ۱۶ مقدار lexical با parser سختگیر و quiet دارد.
آخرین صفحه داده complete:true,validated:false و cursor نهایی دارد. کلاینت همان cursor، total و checkpoint را
با validate:true دوباره میفرستد؛ پاسخ نهایی complete:true,validated:true، position == total، count صفر و
users خالی دارد. جدول خالی هم یک صفحه دادهٔ خالی و همین validation نهایی دارد. validation مصرف را ACK نمیکند.
تغییر config و stats ادامه snapshot فعال را باطل نمیکند. snapshot غایب، منقضی یا جایگزینشده و تغییر checkpoint با stale_cursor رد میشود. total/position غلط invalid_cursor و تناقض epoch/sequence/digest خطای accounting-continuity-lost است. revisionهای صفحه ثابت و متعلق به snapshot هستند؛ envelope revision زنده و ساعت هر پاسخ تازه است. timeout هر صفحه مستقل است و کلاینت تا validation و نصب اتمی cache قبلی را نگه میدارد.
حد traversal کلاینت پیشفرض ۱۰۰۰۰۰ user و ۲۵۶ MiB بایت encoded رکوردهاست؛ RAM واقعی بیشتر است.
AddNewUser
AddNewUser یک object کاربر در قالب JSON میخواهد. کاربر فرستادهشده باید id یا user-id یکتا و غیرصفر داشته باشد.
کل payload پیش از ساخت credential بررسی میشود: UTF-8 معتبر، کلیدهای یکتا پس از
decode، نبود NUL خام یا decodeشده و فقط whitespace پس از object لازم است. ورودی
نامعتبر کاربران، revision تنظیمات و فایلهای دیتابیس را تغییر نمیدهد. بایتهای
رمز Unicode معتبر و aliasهای password و pass معنای قبلی خود را حفظ میکنند.
ماژول نام یا id تکراری و نیز کلید جستوجوی تکراری مشتقشده از password را نمیپذیرد. پس از موفقیت، دیتابیس را بیدرنگ ذخیره میکند. اگر ذخیره شکست بخورد، تلاش میکند افزودن در حافظه را rollback کند و database-save-failed برمیگرداند.
پاسخ storage-unavailable یعنی ظرفیت درخواستی برای ذخیرهسازی یا ایندکس کاربر پذیرفته نشده یا مقداردهی اولیه قفلهای بومی برای نسخه کپیشده کاربر شکست خورده است. تمام شدن حافظه heap به خاتمه فرایند منجر میشود و پاسخ خطا تولید نمیکند.
UpdateUser
درخواست فقط patch تنظیمات است و id دقیق، غیرصفر و unsigned 64-bit برای انتخاب حساب لازم دارد:
{"id":42,"name":"Alice"}
اگر مدیر در ۱۰۰ بایت حساب را خوانده و سپس ۱۰ بایت مصرف ثبت شده باشد، این تغییر نام، مجموع ۱۱۰ بایت را حفظ میکند. password یا object کامل صادرشده کاربر ارسال نمیشود. حذف هر فیلد از درخواست یعنی حفظ مقدار فعلی آن، حتی برای siblingهای object تو در تو:
{"id":42,"limit":{"traffic":{"total":1073741824}},"time":{"expire-after-first-usage-ms":86400000}}
در این مثال فقط quota کل و مدت انقضا پس از اولین استفاده تغییر میکنند؛ limitهای دیگر، انقضای مطلق، زمان ایجاد، اولین استفاده و تمام شمارندهها حفظ میشوند.
| leaf مجاز | نوع |
|---|---|
name، email، notes، wireguard-allowed-ips | string؛ رشته خالی برای پاککردن طبق قواعد index/CIDR |
gid | unsigned 64-bit دقیق |
enabled | boolean واقعی JSON |
record-stat-interval-ms | عدد صحیح نامنفی تا INT_MAX؛ scheduler تازهای اضافه نمیکند |
limit.traffic.up، .down، .total | unsigned 64-bit دقیق |
limit.bandwidth.up، .down | unsigned 64-bit دقیق |
limit.ips، .devices، .connections-in، .connections-out | unsigned 64-bit دقیق |
time.expire-at-ms، .expire-after-first-usage-ms | unsigned 64-bit دقیق |
نقطهها مسیر object تو در تو هستند. صفر limit/expiry مربوط را پاک میکند. token صحیح ساده تا INT64_MAX و رشته
decimal تا UINT64_MAX پذیرفته میشود؛ منفی، کسر، decimal/exponent عددی و overflow رد میشوند.
حداقل یک leaf لازم است؛ patch صرفاً دارای id، container خالی، null، کلید ناشناخته/alias/با case متفاوت و کلید
decodeشده تکراری در هر سطح رد میشود. کل بازه بایتها بررسی میشود: UTF-8 نامعتبر، NUL خام یا escaped، داده اضافی
و بیش از هشت container تو در تو مجاز نیست. بودجه پیام authentication برقرار است، نه محدودیت اندازه فایل config.
stats، password/hash/credential، ID جایگزین، runtime/client-view و زمان ایجاد/اولین استفاده، از جمله aliasهای
سطح بالا، مجاز نیستند. وجود یک فیلد معتبر کنار فیلد ممنوع باعث اعمال جزئی نمیشود.
حساب ناشناخته، نام تکراری، CIDR نامعتبر/همپوشان یا رد ظرفیت، هیچ فیلد، index منطقی، revision یا checkpoint را
تغییر نمیدهد. موفقیت نوع 0 و user-updated است و زیر write lock فقط config_revision را یک بار افزایش میدهد؛
حتی اگر مقدار برابر مقدار فعلی باشد. این ویرایش در حافظه است و ذخیره فوری ندارد. patchهای مستقل فیلدهای حذفشده
از درخواست را حفظ میکنند؛ برای یک فیلد، آخرین ویرایش پذیرفتهشده برنده است. expected-revision لازم نیست.
allow-user-write و allow-stats-push مستقلاند؛ یکی مجوز دیگری نیست. opcode شماره 8 پشتیبانی نمیشود و
unknown-request-type میدهد.
مالکیت آمار
شمارنده و محدودیت ترافیک بر حسب بایت قابلکسر از سهمیه هستند. AuthenticationClient ضریب تنظیمشدهٔ
traffic-multiplier را پیش از گزارش روی بایت واقعی تازه اعمال میکند؛ AuthenticationServer ضریب دیگری اعمال نمیکند.
producerهای دیگر و import دیتابیس نیز باید مصرف قابلکسر بدهند. با ضریب پیشفرض 1 بایت واقعی و قابلکسر برابرند.
مجموع نرخهای متفاوت را نمیتوان به ترافیک واقعی تبدیل کرد؛ سرعت و آمار غیرترافیکی واحد خود را حفظ میکنند.
| مقدار | مالک و قاعده فعلی |
|---|---|
| بایت قابلکسر upload/download تجمعی | مجموع سرور؛ گزارش remote فقط با batch deduplicated؛ patch تنظیمات آن را حفظ میکند |
| کمترین مشاهده غیرصفر اولین استفاده | کمترین مشاهده غیرصفر تولیدکننده مجاز، پس از clamp به زمان حسابداری پذیرش؛ patch آن را حفظ میکند |
| زمان ایجاد | metadata ایجاد/import؛ ویرایش عادی آن را حفظ میکند |
| limit، enabled و سیاست expiry | تنظیم حساب، قابل تغییر فقط با leaf صریح patch |
| اتصال خروجی فعال و reservation IP | وضعیت admission محلی، قابل نگهداری/آزادسازی در runtime؛ تعداد global نیست |
| آمار ذخیره/import شده IP، device، connection و speed | نمای قابل اعتماد از اشغال زنده global یا ورودی admission نیست؛ گزارش تازهای اضافه نشده است |
گزارش آینده connection باید snapshot هر producer با ترتیب نمونه و expiry داشته باشد؛ نمونه بعدی جای قبلی را میگیرد و مثل ترافیک جمع نمیشود. speed به بازه/زمان اندازهگیری و freshness نیاز دارد؛ نمونه مفقود/قدیمی صفر اندازهگیریشده نیست و تجمیع producerها به بازههای سازگار نیاز دارد. IP متمایز به مجموعه هویت، پنجره مشاهده و حذف تکرار بین producerها نیاز دارد؛ جمع تعدادهای کلاینت، تعداد IP متمایز global نمیسازد. device به تعریف هویت و روش جمعآوری نیاز دارد. reset نیازمند تصمیم جدا درباره مصرف با تأخیر و cutoff توزیعشده است و اینجا عملیاتی ندارد.
PushUserStats
تنها مسیر remote گزارش مصرف، PushUserStats و صرفاً برای بایت ترافیک است. شکل دقیق payload:
{"epoch":"<64 hexadecimal bytes>","sequence":1,"users":[{"id":42,"up":10,"down":0,"first-usage-at-ms":1792252800000}]}
ریشه فقط epoch، sequence و users و هر ورودی دقیقاً id، up، down و first-usage-at-ms دارد؛ همه اجباریاند.
فیلد ناشناخته/تکراری، آمار غیرترافیکی، timestamp دیگر یا مجموع مطلق، کل درخواست را رد میکند. قواعد strict JSON/UTF-8،
NUL، عمق سه container، ۳۶۸۷۱ مقدار lexical و مصرف کامل ورودی برقرار است. digest از بایتهای اصلی payload محاسبه میشود.
هر batch بین ۱ تا ۴۰۹۶ ID یکتا و غیرصفر دارد. هر ورودی هر دو delta و timestamp غیرصفر دارد؛ هر دو delta برای گزارش فقط metadata میتوانند صفر باشند. tokenهای integer ساده تا INT64_MAX و رشته decimal تا UINT64_MAX دقیقاند؛ decimal/exponent عددی،
مقادیر کسری، منفی، nonfinite و overflow برای استخراج عدد صحیح رد میشوند.
checkpoint با هویت احرازشده انتخاب میشود. زیر یک write lock، replay آخرین sequence با digest یکسان پیش از بررسی دوباره حسابها ACK میشود. برای sequence دقیقاً بعدی، همه ورودیها پیش از mutation بررسی میشوند؛ سپس ساعت حسابداری یک بار نمونهگیری میشود؛ مشاهده به حداکثر همان زمان clamp و کمترین مقدار غیرصفر با canonical merge میشود. مصرف saturating، اولین استفاده و checkpoint با یک افزایش stats_revision منتشر میشوند؛ config_revision تغییر نمیکند. گزارش بعدی همان timestamp محلی را حفظ میکند؛ receipt-time fallback وجود ندارد. ID ناشناخته، تکرار ID، epoch قدیمی، gap، sequence قدیمی یا محتوای متفاوت همان sequence، بدون mutation رد میشود. sequence wrap مجاز نیست.
ACK موفق نوع 0 و JSON دارای epoch، sequence، digest شصتوچهارکاراکتری SHA-256 و needs-pull: true
است. replay دقیق حتی پس از تغییر یا حذف حساب ACK میشود و همچنان pull زمان اولین استفاده و مجموع مرجع را لازم
میداند. تاریخچه checkpoint ثابت و محدود به آخرین batch هر هویت است؛ session یک reference به snapshot مشترک و محدود جدول دارد.
کلاینت پس از reconnect ابتدا batch باقیمانده را replay و ACK آن را اعتبارسنجی میکند، سپس جدول کامل را میگیرد.
دریافت الزامی جدول پس از احراز هویت یا هر ACK باید پیش از ساخت batch جدید تمام شود. پس از آن، push منتظر بر
pullهای عادی دورهای یا صریح اولویت دارد تا pullهای مکرر باعث توقف نامحدود گزارش مصرف نشوند.
رفتار Lifecycle
با دریافت init در جهت upstream، AuthenticationServer وضعیت buffer مخصوص همان line را مقداردهی اولیه میکند و est را در جهت downstream به تونل قبلی میفرستد.
در payloadِ upstream، بایتها را buffer میکند، پیامهای بیرونی کامل را بیرون میکشد، هر فریم درخواست را پردازش میکند و با tunnelPrevDownStreamPayload() یک پیام پاسخ ترکیبی در جهت downstream میفرستد.
با دریافت finish در جهت upstream، فقط state مخصوص همان line را از بین میبرد. چون سازنده line نبوده، lineDestroy() را فراخوانی نمیکند.
اگر سرور به دلیل داده malformed یا بزرگتر از حد مجاز اتصال منطقی را ببندد، ابتدا state خودش برای line را از بین میبرد و سپس finish را در جهت downstream به تونل قبلی میفرستد.
محدودیت احراز هویت و نگهداری ورودی
سرور پس از دریافت چهار بایت طول و ۶۴ بایت token، اعتبار token را زیر قفل مشترک بررسی میکند.
بدون token معتبر، سقف payload فریمها 16 KiB جدا از ۶۸ بایت پاکت است؛ اعلام طول بزرگتر
بلافاصله و بدون انتظار برای body رد میشود. token هنگام dispatch دوباره بررسی میشود؛ پیام بزرگ
با token منقضی یا جایگزینشده اجرا نمیشود. پیام کوچک همان خطای correlated authentication-required را دارد.
همهٔ فریمها پیش از اجرا بررسی میشوند و هر پاکت حداکثر ۶۴ فریم دارد؛ فریم ۶۵ مانع اجرای همهٔ
فریمهای قبلی میشود. session زمان ورود پاکت برای همهٔ siblingها حاکم است؛ موفقیت Authenticate
به درخواست کنار آن مجوز نمیدهد. دادهٔ Authenticate حداکثر ۱۶۳۷۵ بایت است و parser سختگیر و
بیصدای runtime با depth دو و هشت مقدار lexical فقط name/secret غیرخالی و epoch معتبر
اختیاری را میپذیرد. duplicate/unknown key، trailing data، encoding نامعتبر و NUL رد میشوند.
هر دو peer هنگام startup اندازهٔ دقیق JSON escapeشده همراه epoch کامل ۶۴بایتی را بررسی میکنند؛
credential بزرگتر رد میشود و هرگز truncate نمیشود.
پس از نخستین Authenticate ناموفق، پذیرش ورودی پیش از تحویل پاسخ بسته میشود. صف محدود پاسخ از Pause/Resume پیروی میکند و ورودی بعدی اتصال را میبندد. refusal بدون هویت producer هیچ slot صف audit یا dropped-event مصرف نمیکند؛ commit همچنان ثبت و refusal دارای هویت طبق سیاست کل node نمونهبرداری میشود.
ورودی تأییدنشده پس از compaction حداکثر 16 KiB + 68 bytes، ۶۴ fragment و charge برابر بزرگترِ
128 KiB یا یک allocation بهترین pool برای پاکت بیشینه دارد. nested input و unread suffix قدیمی
از allowance مشترک استفاده میکنند؛ معتبر بودن پیام اول به successor بزرگِ بدون token مجوز نمیدهد.
حد معمول nested input فقط با شاهد token معتبر اعمال میشود. carrier جاری callback، پیام استخراجشده
و overlap موقت compaction جدا هستند. ظرفیت ساخت پاسخ بهصورت هندسی و داخل سقف رشد میکند؛ قدیم و جدید
ممکن است موقتاً همزمان باشند. خطاهای lexical runtime diagnostic تنظیمات روی stderr نمینویسند.
full-table sync یک پاسخ serialized زیر سقف حدود 16 MiB، شامل metadata و framing، لازم دارد.
جدول بزرگتر فعلاً sync نمیشود و شکست pull الزامی میتواند push مصرف را عقب بیندازد؛ این حد بایت
است، نه تعداد ثابت کاربر. pagination کار جداگانه است.
این پروتکل credential تولیدکننده و همهٔ دادهٔ کاربران از جمله credential آنها را حمل میکند.
برای لینک remote نامطمئن، transport رمزگذاریشده با احراز هویت و بررسی هویت سرور لازم است:
AuthenticationClient -> TlsClient -> TcpConnector با تنظیمات
{"sni":"auth.example.com","verify":true} و سمت سرور
TcpListener -> TlsServer -> AuthenticationServer با certificate و key سرور.
مثال loopback برای استفادهٔ محلی است؛ MAC پاسخ بهتنهایی محرمانگی نمیدهد.
نکتهها
- سقف payload پیام بیرونی با token معتبر
16 MiBو بدون آن16 KiBاست. - سقف داده درخواست
16 MiBاست. - سقف payload پاسخ
16 MiBاست. - پاسخهای صفشده و فعال مشترکاً ۱۰۲۴ entry،
16 MiB + 20 bytesداده منطقی و32 MiBallocation charge دارند. refusal فقط همان line را میبندد. خروجی با chunkهای حداکثر16 KiB، padding کافی و FIFO در برابر reentry ارسال میشود؛ backing فعال تا آزاد شدن charge دارد. این سقف RSS کل نیست. - scratch ساخت شامل یک frame، یک پیام در حال رشد، یک متن JSON محدود و یک chunk خروجی است؛ هر پیام/frame حداکثر
16 MiBبهاضافه envelope است و printer پنج بایت اضافه دارد. هنگام تعویض allocation، قدیم و جدید موقتاً همزماناند. - پیامهای کامل درخواست قبل از بررسی سقف ورودی ناقص پردازش میشوند؛ چند پیام در یک تحویل، یک پیام بیشازحد بزرگ نیست.
ورودی ناقص با token معتبر کمتر از
16 MiB + 68 bytesاست و پس از ادغام بافرها حداکثر ۱۰۲۴ entry و32 MiBcharge دارد. ورودی تودرتو همراه با ادامهٔ خواندهنشدهٔ قبلی، سقف یک پیام کامل بهاضافهٔ2 MiBرا دارد؛ بافرهای قطعهقطعه پیش از رد ورودی ادغام میشوند. تحویل خالی آزاد میشود. یک درخواست استخراجشده و مقصد ادغام، scratch محدود هستند؛ تحویل همگام فعلی خارج از سقف ورودی نگهداریشده است. - این node به left padding نیاز ندارد و چیزی را درجا prepend نمیکند.
- این node packet tunnel نیست و از قواعد packet line استفاده نمیکند.
عملکرد پایگاه داده کاربران
- SHA-256 کلید اصلی جستوجوی password متنی است. هر جستوجوی password فقط یک بار SHA-256 را محاسبه میکند و بدون fallback scan روی جدول کاربران، از index هش SHA-256 نتیجه را پیدا میکند؛ بنابراین هزینه متوسط hit و miss برابر O(1) است. مقایسه دقیق password متنی پس از hit شدن index، بررسی نهایی و مقاوم در برابر برخورد hash است.
- تمام جستوجوهای کلیددار، شامل SHA-224، SHA-256، UUID، کلید عمومی WireGuard، id ماندگار و name غیرخالی، هزینه متوسط O(1) دارند. جستوجوی آدرس Allowed-IP مربوط به WireGuard و تشخیص همپوشانی rangeها با یک index بازهای مرتبشده و هزینه O(log M) انجام میشود.
Pingو credential lookup از state read lock استفاده میکنند.GetUsersPageبرای ساخت snapshot و مدیریت reference/lease، مانند احراز هویت و تغییر کاربر/آمار state write lock میگیرد؛ envelope ترکیبی قویترین lock لازم در descriptorهای module را میگیرد.
API مدیریتی
سرور operationهای فقطخواندنی status، get-user و list-users و operation
نوشتنی مشروط update-user، ساخت حساب create-user و تغییر رمز
change-user-password را صریحاً برای APIServer ثبت میکند. بدنهٔ درخواست application/json و پاسخ
JSON با UTF-8 است. token مستقل مدیر و مجوز دقیق را در gateway تنظیم کنید:
{"config":"central","node":"auth-db","operations":["status","get-user","list-users"]}
مجوز status دسترسی به جزئیات یا فهرست کاربران نمیدهد. مجوز get-user یا
list-users شامل همهٔ حسابهای سرور انتخابشده است؛ cursor مجوز نیست و هر
ادامهٔ صفحه باید دوباره احراز هویت و مجوزسنجی شود. مجوز ویرایش، ساخت حساب و تغییر رمز هرکدام جداگانه داده میشوند. خواندن، حسابی را تغییر
نمیدهد، ترافیک را احراز هویت نمیکند، همگامسازی انجام نمیدهد و ذخیرهٔ دیتابیس
را آغاز نمیکند. رمز، hash، اعتبارنامهٔ UUID، کلید WireGuard، secret تولیدکننده
و token نشست هرگز در این نماها قرار نمیگیرند.
بدنه باید object سختگیرانهٔ JSON باشد؛ کلید ناشناخته/تکراری، UTF-8 نامعتبر،
NUL decodeشده، دادهٔ اضافه و نوع نادرست رد میشوند. سقف بدنهٔ خواندن ۴۰۹۶ بایت، عمق دو
و ۱۶ مقدار lexical است؛ تغییر حساب محدودیتهای جداگانهٔ زیر را دارد. تمام مقدارهای عددی پاسخ، از شناسه و revision تا شمارنده،
محدودیت، بایت و زمان، رشتهٔ دهدهی unsigned canonical هستند؛ enabled
boolean میماند. شناسهٔ ورودی نیز رشتهای از "1" تا
"18446744073709551615"، بدون علامت یا صفر ابتدایی است؛ بنابراین شناسههای
بزرگتر از محدودهٔ دقیق عدد در JavaScript بدون تغییر منتقل میشوند.
هر پاسخ موفق خواندن یک edit_token سطح اصلی دارد که همراه revision تنظیمات
نمونهبرداری میشود. این مقدار opaque با ۴۸ کاراکتر hex کوچک، نمونهٔ سرور و
revision را به هم پیوند میدهد و شرط خوشبینانهٔ ویرایش است، نه اعتبارنامهٔ
احراز هویت. هر صفحهٔ فهرست یک token دارد؛ cursor صفحهبندی همچنان ۶۴ کاراکتری
و جدا از آن است.
status
بدنه دقیقاً {} است. پاسخ موفق:
{
"database_loaded": true,
"database_dirty": false,
"primary_checkpoint_valid": true,
"user_count": "1200",
"configured_producer_count": "4",
"session_record_count": "5",
"config_revision": "84",
"edit_token": "000102030405060708090a0b0c0d0e0f0000000000000054",
"stats_revision": "1902",
"primary_save": {
"last_result": "success",
"last_attempt_age_ms": "3200",
"last_success_age_ms": "3200"
},
"audit": {
"enabled": false,
"healthy": true,
"queued_records": "0",
"written_records": "0",
"dropped_records": "0",
"suppressed_rejections": "0",
"write_failures": "0",
"last_error": null
}
}
session_record_count تعداد رکورد نشست نگهداشتهشده است، نه اتصال زنده، کاربر
آنلاین یا دستگاه. تاریخچهٔ ذخیره فقط متعلق به همین فرایند است. last_result
یکی از not_attempted، success یا failure است؛ پیش از نخستین ذخیرهٔ runtime
هر دو age برابر null هستند. بارگذاری یا بازیابی دیتابیس تلاش ذخیره محسوب
نمیشود. ageها زمان سپریشدهٔ monotonic در پایان تصمیم ذخیرهٔ اصلیاند و تغییر
ساعت دیواری روی آنها اثر ندارد. شکست بعدی، زمان موفقیت قبلی را حفظ میکند.
مرز commit همان جایگزینی موجود فایل اصلی است؛ خطای backup تاریخی اختیاری یا
هشدار directory-sync پس از آن موفقیت را به شکست تبدیل نمیکند و تضمین قویتر
در برابر قطع برق ایجاد نمیکند.
get-user
درخواست {"id":"42"} است. پاسخ شامل config_revision، stats_revision،
accounting_time_ms، edit_token و user است. فیلدهای user دقیقاً اینها هستند:
| فیلدها | معنا |
|---|---|
id، gid، enabled | هویت پایدار، گروه و تنظیم فعالبودن. |
name، email، notes، wireguard_allowed_ips | رشتههای کامل مجاز؛ مقدار ذخیرهنشده رشتهٔ خالی است. هیچ کلیدی ارائه نمیشود. |
record_stat_interval_ms | فاصلهٔ تنظیمشدهٔ ثبت آمار. |
limit.traffic.u، .d، .total | محدودیت ارسال، دریافت و مجموع بر حسب بایت قابلکسر از سهمیه. |
limit.bandwidth.u، .d | بایت منطقی واقعی در ثانیه برای هر جهت؛ صفر نامحدود است. UserController سهم هر user را بین lineها و workerهای همان AuthenticationClient مشترک اعمال میکند. |
limit.ips، .devices، .cons_in، .cons_out | تنظیمات محدودیت؛ حضور آنها به معنی اجرای همهٔ محدودیتها در تمام nodeها نیست. |
time.created_at_ms، .expire_at_ms | زمان ایجاد و انقضای مطلق با ساعت دیواری موجود. |
time.expire_after_first_usage_ms | مدت انقضای نسبی. |
time.first_usage_at_ms | مشاهدهٔ نخستین مصرف با ساعت حسابداری سرور. |
usage.u، .d | مصرف قابلکسر تجمعی پذیرفتهشده توسط سرور. |
صفرها و پیشفرضها صریحاند و معنای sentinel فعلی را حفظ میکنند.
accounting_time_ms مرجع فعلی ساعت حسابداری است که همراه حساب و revisionها
نمونهبرداری میشود؛ نما تصمیم مشتقشدهٔ انقضا یا پذیرش نمیسازد. کلاینت قطعشده
ممکن است مصرف گزارشنشده داشته باشد. gaugeهای ذخیرهشدهٔ سرعت، اتصال، IP و
دستگاه آمار زندهٔ سراسری نیستند و همراه وضعیت محلی اجرا/حسابداری کلاینت حذف
میشوند.
شناسهٔ معتبرِ غایب not_found میدهد. جزئیات کوتاه نمیشوند؛ رکورد بزرگتر از
بودجه record_too_large و رشتهٔ ذخیرهشده با UTF-8 نامعتبر
invalid_record_encoding میدهد.
list-users
{} پیمایش تازه را با limit پیشفرض ۵۰ آغاز میکند. {"limit":1} عدد صحیح
۱ تا ۱۰۰ را انتخاب میکند؛ برای ادامه
{"limit":50,"cursor":"<returned opaque cursor>"} را بفرستید. limit میان
صفحهها قابل تغییر است. cursor خالی، null یا خراب معتبر نیست.
{
"config_revision":"84",
"edit_token":"000102030405060708090a0b0c0d0e0f0000000000000054",
"stats_revision":"1902",
"accounting_time_ms":"1800000000000",
"total_users":"1200",
"users":[{
"id":"42","gid":"7","enabled":true,
"name_preview":"Alice","name_preview_state":"complete",
"traffic_limit":{"u":"0","d":"0","total":"1000000000"},
"usage":{"u":"2500","d":"8000"},
"time":{"expire_at_ms":"0","expire_after_first_usage_ms":"86400000","first_usage_at_ms":"1800000000000"}
}],
"next_cursor":"<returned opaque cursor>"
}
خلاصه دقیقاً فیلدهای بالا را دارد. برای سایر جزئیات مجاز از get-user استفاده
کنید. name_preview حداکثر ۲۵۶ بایت UTF-8 است و در مرز codepoint پایان مییابد.
حالت آن complete، truncated یا invalid_encoding است؛ در حالت آخر preview
برابر null میشود و پیمایش ادامه دارد. دنبالهٔ بررسینشدهٔ نام بلند اعتبارسنجی
نمیشود؛ email و notes در فهرست اسکن نمیشوند.
ترتیب همان ترتیب collection است و مرتبسازی نام/شناسه تضمین نمیشود. cursor
۶۴ کاراکتری opaque و متعلق به همین نمونهٔ فرایند است. هر تغییر پذیرفتهشدهٔ
revision تنظیمات، حتی patch با مقدار یکسان یا افزودن حساب، cursor قبلی را با
stale_cursor رد میکند؛ بدون cursor دوباره آغاز کنید. بازسازی/راهاندازی مجدد
سرور نیز cursor را باطل میکند. تغییر صرف آمار و مشاهدهٔ نخستین مصرف آن را باطل
نمیکند؛ هر صفحه میتواند مصرف تازهتری داشته باشد. صفحهها snapshot ثابت
ترافیک نیستند. با تنظیمات ثابت هر حساب یکبار دیده میشود و next_cursor: null
پایان را نشان میدهد.
limit سقف است؛ محدودیت حافظه یا پاسخ میتواند صفحهٔ غیرخالی کوچکتری ایجاد کند،
بدون ردکردن رکوردهای برنگشته. اگر حتی یک خلاصه جا نشود response_too_large
برمیگردد. timeout یا رقابت lock، کپی ناقص را دور میاندازد و cursor موفق ناقص
تولید نمیکند.
update-user
ویرایشگر به مجوز صریح get-user و update-user نیاز دارد. token مانیتور را
فقطخواندنی نگه دارید؛ اعتبارنامهٔ تولیدکننده و allow-user-write دسترسی
APIServer نمیدهند. ابتدا حساب را بخوانید و edit_token آن را در if_match
قرار دهید:
{
"id":"42",
"if_match":"000102030405060708090a0b0c0d0e0f0000000000000054",
"patch":{
"enabled":false,
"limit":{"traffic":{"total":"1000000000"}},
"time":{"expire_at_ms":"0"}
}
}
دقیقاً سه کلید id، if_match و patch غیرخالی اجباریاند. نامها در این API
به صورت snake_case و جهتهای ترافیک u/d هستند؛ املای پروتکل تولیدکننده
تغییر نمیکند. فقط برگهای مورد نظر را بفرستید؛ مقدارها و siblingهای حذفشده
از درخواست، بدون تغییر میمانند.
| برگهای patch | مقدار مجاز |
|---|---|
name، email، notes، wireguard_allowed_ips | رشتهٔ UTF-8؛ رشتهٔ خالی برای پاککردن. یکتایی نام و اعتبار/عدم همپوشانی Allowed-IP همچنان اعمال میشود. |
gid | رشتهٔ دهدهی unsigned canonical، شامل "0". |
enabled | boolean در JSON. |
record_stat_interval_ms | رشتهٔ دهدهی از "0" تا INT_MAX. |
limit.traffic.u، .d، .total؛ limit.bandwidth.u، .d | رشتهٔ دهدهی unsigned؛ traffic بایت سهمیه و bandwidth بایت منطقی واقعی در ثانیه است. |
limit.ips، .devices، .cons_in، .cons_out | رشتهٔ دهدهی unsigned. |
time.expire_at_ms، .expire_after_first_usage_ms | رشتهٔ دهدهی unsigned با حفظ sentinel صفر و معنای ساعت/مدت فعلی. |
اعداد unsigned جز محدودیت بازهٔ interval تا UINT64_MAX مجازند. علامت، فاصله،
صفر ابتدایی، کسر و overflow رد میشوند؛ شناسهٔ حساب همچنان غیرصفر است.
object تودرتوی خالی، null، کلید ناشناخته/تکراری، نوع نادرست یا فیلد محافظتشده
کل patch را رد میکند. ID، رمز و کلید مشتقشده، زمان ایجاد/نخستین مصرف، usage،
آمار، reservationهای runtime و checkpoint حسابداری قابل ویرایش نیستند.
کل خروجی get-user را بهعنوان patch نفرستید. حساب غیرفعال یا منقضی هم قابل
ویرایش است؛ تنظیم محدودیت قابلیت اجرایی جدید به nodeهای دیگر اضافه نمیکند.
پاسخ موفق:
{
"id":"42",
"config_revision":"85",
"edit_token":"000102030405060708090a0b0c0d0e0f0000000000000055"
}
شرط باید هم نمونهٔ سرور و هم revision سراسری فعلی تنظیمات را تطبیق دهد.
ویرایش حسابی دیگر نیز میتواند conflict ایجاد کند؛ token نمونهٔ قبلی سرور هم
رد میشود. هر patch پذیرفتهشده، حتی مقدار یکسان، revision را یک بار افزایش
میدهد و token همان تراکنش را برمیگرداند. push آمار و replay دقیق، token
ویرایش را باطل نمیکنند. token قدیمی پس از موفقیت، تغییر را دوباره اعمال نمیکند.
موفقیت فقط commit در حافظه را تأیید میکند، نه ذخیرهٔ دیسک، refresh کلاینت
یا قطع فوری ترافیک موجود. API ذخیرهای آغاز نمیکند و تاریخچهٔ primary-save را
تغییر نمیدهد؛ ذخیرهٔ دورهای/نهایی موجود ادامه دارد و crash میتواند تغییرات
ذخیرهنشده را از بین ببرد. کلاینت متصل از همگامسازی عادی آگاه میشود؛ کلاینت
قطعشده تا refresh از سیاست cacheشده استفاده میکند. مصرف، نخستین مصرف،
اعتبارنامهها، runtime، checkpoint حسابداری و stats_revision حفظ میشوند.
ویرایش مجاز پروتکل تولیدکننده همان revision تنظیمات را دارد: ویرایش پیشین آن
میتواند token API را باطل کند و ویرایش بعدی آن همچنان نوشتن بعدی است.
سقف بدنهٔ ویرایش ۳۲ KiB، عمق چهار object و ۶۴ مقدار lexical است. آمادهسازی،
کار index، deadline و charge حافظه پیش از commit محدود میشوند. تغییر نام با
نام ذخیرهشدهٔ بزرگتر از سقف بازرسی ۳۲ KiB، resource_limit میدهد؛ ویرایش
فیلدهای دیگر آن نام را بررسی نمیکند. رشد تازهٔ index Allowed-IP حداکثر ۱ MiB
و نیز محدود به بودجهٔ باقیمانده است. اینها سقف operation هستند، نه محدودیت
تازهٔ import دیتابیس. امتناع معمول، مقدارها، عضویت index و revisionها را تغییر
نمیدهد، گرچه ظرفیت رزروشدهٔ خصوصی میتواند باقی بماند. ACK حداکثر ۲۵۶ بایت
است و پیش از تغییر آماده میشود؛ حتی با ID درخواست کاملاً escapeشده در سقف
حداقل ۱۰۲۴ بایتی پاکت gateway جا میگیرد.
کدهای خطای operation عبارتاند از invalid_request، conflict، busy،
not_found، invalid_allowed_ips، name_exists، allowed_ips_overlap،
resource_limit، response_too_large، timeout و revision_exhausted.
خطای بازگشتی سطح operation یعنی این ویرایش commit نشده است. conflict token
تازه نمیدهد؛ دوباره بخوانید و قصد تغییر را بازبینی کنید. در revision برابر
UINT64_MAX ویرایشهای API و پروتکل تولیدکننده پیش از تغییر حساب رد میشوند.
گمشدن پاسخ، قطع اتصال، timeout دیرهنگام gateway یا شکست تحویل پس از dispatch یعنی نتیجه نامعلوم است و تغییر ممکن است commit شده باشد. ابتدا حساب را دوباره بخوانید، مقدارهای مورد نظر را با معنای معمولشان، از جمله Allowed-IP نرمالشده، مقایسه کنید و سپس دربارهٔ patch تازه تصمیم بگیرید. گرفتن خودکار token تازه و replay کردن patch قبلی ممنوع است؛ ID درخواست فقط correlation است و deduplication یا رکورد idempotency پایدار فراهم نمیکند. کلاینت نمونه نوشتن را خودکار تکرار نمیکند.
create-user
ساخت حساب به مجوز دقیق و مستقل create-user در gateway نیاز دارد. مجوز
update-user ویرایشگر، اعتبارنامهٔ تولیدکننده و allow-user-write آن را
اعطا نمیکنند. نقش creator معمولاً برای بررسی نتیجهٔ نامعلوم به get-user
هم نیاز دارد. فراخواننده ID عددی یکتا و ماندگار و رمز اولیه را میدهد؛
edit token لازم نیست:
{
"id":"43",
"password":"example-only-secret",
"config":{
"name":"alice",
"enabled":false,
"limit":{"traffic":{"total":"100000000000"}},
"time":{"expire_after_first_usage_ms":"2592000000"}
}
}
حذف config یا ارسال {} حساب فعال با محدودیتها و انقضای صفرِ
نامحدود/تنظیمنشده میسازد. برای آمادهسازی مرحلهای صریحاً
"enabled":false بفرستید. پیشفرضها همان ساخت معمول حساب تازه هستند:
| برگ تنظیمات | پیشفرض |
|---|---|
name، email، notes، wireguard_allowed_ips | رشتهٔ خالی |
gid | "0" |
enabled | true |
record_stat_interval_ms | "120000" |
limit.traffic.u، .d، .total؛ limit.bandwidth.u، .d | "0" |
limit.ips، .devices، .cons_in، .cons_out | "0" |
time.expire_at_ms، .expire_after_first_usage_ms | "0" |
اینها دقیقاً همان ۱۸ برگ عمومی update-user با نوع، بازهٔ عدد canonical،
معنای صفر، یکتایی نام و نرمالسازی/عدم همپوشانی Allowed-IP هستند. فقط خود
config میتواند خالی باشد؛ limit، traffic، bandwidth و time تودرتو
اگر ارسال شوند باید غیرخالی باشند. ID، رمز، کلید مشتقشده، زمان ایجاد/نخستین
مصرف، usage، آمار، runtime و checkpoint در config مجاز نیستند. تنظیم محدودیت
قابلیت اجرایی تازهای به nodeهای دیگر اضافه نمیکند.
کلیدهای root فقط id و password اجباری و config اختیاری هستند. ID رشتهٔ
دهدهی unsigned canonical غیرصفر تا UINT64_MAX است و مقدار بالاتر از محدودهٔ
دقیق JavaScript را حفظ میکند. رمز، UTF-8 غیرخالی با بایتهای دقیق ورودی است؛
trim، نرمالسازی یا سیاست تازهٔ قدرت رمز اعمال نمیشود. NUL decodeشده، UTF-8
نامعتبر، عدد JSON برای عدد صحیح، علامت، صفر ابتدایی، null، کلید ناشناخته/تکراری،
alias پروتکل و دادهٔ اضافی رد میشوند. if_match، edit_token و request_id
در root مجاز نیستند. پیش از ساخت درخت JSON، بدنه حداکثر ۳۲ KiB، عمق چهار و
۶۴ مقدار lexical شامل کلیدها دارد.
SHA-224، SHA-256 و هویت WireGuard با helperهای معمول از رمز مشتق میشوند؛ هویت UUID فقط برای رمز با قالب UUID وجود دارد. سرور زمان ایجاد را با ساعت wall-clock سازنده تعیین میکند. مصرف، نخستین مصرف، شمارنده/reservationهای runtime و زمانبندی محلی کلاینت صفر یا خالی شروع میشوند. ساخت، خواندن و همگامسازی، نخستین ترافیک نیستند. رمز و هویتهای مشتقشده در هیچ پاسخ عمومی، از جمله نماهای خواندن، قرار نمیگیرند.
پاسخ موفق فقط ID و revision/token همان تراکنش است:
{
"id":"43",
"config_revision":"86",
"edit_token":"000102030405060708090a0b0c0d0e0f0000000000000056"
}
ساخت موفق revision تنظیمات را یک بار افزایش میدهد و cursorها و edit tokenهای قدیمی را باطل میکند. revision آمار، حسابهای موجود و checkpoint تولیدکننده حفظ میشوند. دو ساخت مستقل معتبر میتوانند موفق شوند؛ ویرایش نامرتبط بهتنهایی ساخت را باطل نمیکند. token پاسخ برای ویرایش مشروط بعدی، تا وقتی جاری است، قابل استفاده است.
ساخت، جایگزینی یا upsert نیست. ID موجود حتی با درخواست یکسان id_exists
میدهد؛ حساب را تغییر نمیدهد و replay موفق تلقی نمیشود. زیر قفل collection،
ID پیش از سایر تکرارها بررسی میشود. نام غیرخالی تکراری name_exists و تکرار
SHA-224، SHA-256، UUID یا WireGuard، credentials_exist بدون معرفی حساب
متعارض میدهد. دو متن رمز متفاوت هم ممکن است هویت مشتقشدهٔ مشترک داشته باشند.
دو درخواست همزمان با ID یکسان حداکثر یک موفقیت و یک busy یا id_exists
دارند؛ تلاش بعدی همان ID، id_exists میگیرد.
کدهای خطای node عبارتاند از invalid_request، id_exists، name_exists،
credentials_exist، invalid_allowed_ips، allowed_ips_overlap، busy،
resource_limit، creation_failed، response_too_large، timeout و
revision_exhausted در قالب ثابت {"error":{"code":"id_exists"}}.
شکست backend/crypto یا مقداردهی قفل native برابر creation_failed است.
خطای ورودی، بودجه، backend یا رقابت میتواند زودتر از بررسی تکرار رخ دهد.
خطای بازگشتی سطح operation تضمین میکند هیچ حسابی منتشر نشده است.
آمادهسازی، مجموع تخصیص تازهٔ storage/index، scratch، کپیهای همزمان و charge
واقعی pool پاسخ باید در بودجهٔ باقیماندهٔ handler جا شوند. resource_limit
ممکن است به handler_working_memory بزرگتر APIServer نیاز داشته باشد؛ سقف
کلی رشد ۱ MiB مخصوص ساخت وجود ندارد. هنگام رشد name map، مجموع بایت نامهای
ذخیرهشده با بودجهٔ کار برابر working allowance بازرسی میشود؛ نامهای قدیمی
بزرگ میتوانند رشد را رد کنند. با ظرفیت آزاد، این scan کامل انجام نمیشود.
بررسی deadline بین reserveهای محدود انجام میشود؛ crypto و rehash بومی کار
همگام محدودند و تضمین سخت زمان پاسخ ندارند. ظرفیت خصوصی میتواند پس از امتناع
باقی بماند، اما عضویت، شمارنده، اشارهگر پایدار و revision تغییر نمیکنند.
ACK حداکثر ۲۵۶ بایتی و charge واقعی آن پیش از انتشار رزرو و render میشوند و
با ID correlation کاملاً escapeشده در حداقل پاکت ۱۰۲۴ بایتی جا میگیرند.
موفقیت فقط commit در حافظه است. API ذخیره را آغاز نمیکند و telemetry
primary-save را تغییر نمیدهد. ذخیرهٔ دورهای/نهایی بعداً آن را ماندگار میکند؛
crash میتواند حساب ذخیرهنشده را از بین ببرد. AddNewUser تولیدکننده همان
ذخیرهٔ همگام و rollback موجود را حفظ میکند. AuthenticationClient متصل حساب
و اعتبارنامه را از sync عادی میگیرد؛ کلاینت offline تا refresh حساب تازه را
نمیشناسد. مصرف معلق حسابهای موجود حفظ میشود. ACK به معنی refresh همه نیست.
گمشدن پاسخ، قطع اتصال، رهاشدن completion یا timeout پس از dispatch یعنی
نتیجه نامعلوم است. ID و ورودی اولیه را نگه دارید و همان ID را با get-user
بخوانید. وجود حساب و تنظیمات عمومی ثابت نمیکند درخواست شما آن را ساخته یا
رمز آن پس از ساخت رقیب با رمز شما برابر است. اگر غایب بود، دربارهٔ تکرار همان
ID و ورودی صریحاً تصمیم بگیرید؛ درخواست اول شاید هنوز اجرا شود. خودکار ID تازه
انتخاب نکنید، حساب موجود را جایگزین نکنید، رمز را reset نکنید و id_exists
را مدرک موفقیت قبلی ندانید. ID بیرونی فقط correlation است، نه idempotency.
فراخواننده IDها را تخصیص میدهد و نباید هویت یک شخص را به شخص دیگر بدهد؛
یکتایی فقط در collection فعلی تضمین میشود، نه تاریخچهٔ پایدار همهٔ IDها یا
exactly-once بین crashها. کلاینت نمونه نوشتن را خودکار تکرار نمیکند.
change-user-password
این operation مدیریتی مجوز دقیق و جداگانه دارد. مدیر رمز معمولاً فقط
get-user و change-user-password برای سرور انتخابشده دارد؛ مجوز مانیتور،
ویرایشگر، سازنده یا تولیدکننده دسترسی تغییر رمز نمیدهد. ابتدا حساب را بخوانید
و دقیقاً این سه فیلد را ارسال کنید:
{
"id":"42",
"if_match":"000102030405060708090a0b0c0d0e0f0000000000000056",
"password":"example-only-new-secret"
}
id رشتهٔ دهدهی unsigned canonical و غیرصفر تا UINT64_MAX است. if_match
همان edit token فعلی با ۴۸ کاراکتر hex کوچک است. password رشتهٔ UTF-8
غیرخالی است که بایتهای decodeشدهٔ آن بدون trim، تغییر حروف یا نرمالسازی Unicode
حفظ میشوند. challenge رمز قبلی، تولید رمز، سیاست قدرت/تاریخچه یا نام جایگزین
new_password وجود ندارد. کلید گمشده/اضافی/تکراری، نوع نادرست، object تودرتو،
null، BOM، UTF-8/Unicode escape خراب، NUL decodeشده و دادهٔ اضافه رد میشوند.
پیش از ساخت درخت JSON، سقف بدنه ۳۲ KiB، عمق یک و ۱۶ مقدار lexical شامل کلیدها
بررسی میشود؛ محدودیت import یا سایر operationها تغییر نمیکند.
زیر قفل سرور، نمونه و revision سراسری تنظیمات پیش از lookup حساب باید تطبیق
داشته باشند. ویرایش/ایجاد حسابی دیگر میتواند conflict بدهد؛ آمار، مشاهدهٔ
نخستین مصرف و replay دقیق usage token را باطل نمیکنند. ارسال رمز فعلی با
شرط تازه نیز پذیرفته و revision را دقیقاً یک بار زیاد میکند؛ این replay success
نیست و token قبلی conflict میدهد. حساب غیرفعال، منقضی یا تمامشدهٔ سهمیه همچنان
از نظر مدیریت قابل تغییر رمز است.
پاسخ موفق همان schema فشردهٔ تغییرهاست و هیچ credential ندارد:
{
"id":"42",
"config_revision":"87",
"edit_token":"000102030405060708090a0b0c0d0e0f0000000000000057"
}
این پاسخ commit همان تراکنش در حافظه را نشان میدهد؛ tokenها و cursorهای قبلی باطل میشوند. در UINT64_MAX عملیات پیش از تغییر رد میشود؛ رسیدن از MAX-1 به MAX مجاز است. همان ACK رزروشدهٔ ۲۵۶ بایتی و اثبات سقف حداقل ۱۰۲۴ بایتی اعمال میشوند.
مقادیر مشتقشده همان ساخت عادی کاربر هستند: hash رمز، SHA-224، SHA-256، اعتبار
UUID و کلید عمومی WireGuard. فقط credentialها و عضویت چهار index آنها تغییر
میکند. نشانی/ID/order/count حساب، نام/گروه/فعالبودن/اطلاعات تماس، سیاستها،
Allowed-IP، زمان ایجاد/نخستین مصرف، آمار، ترافیک و reservationها حفظ میشوند؛
stats_revision و epoch/sequence/digest تولیدکننده تغییر نمیکنند.
هر هویت مشتقشدهٔ معتبر متعلق به حساب دیگر، خطای عمومی credentials_exist
میدهد و حساب یا کلید متعارض را آشکار نمیکند؛ کلید متعلق به خود حساب مجاز است.
plain به UUID، هویت UUID را اضافه میکند؛ UUID به plain آن را حذف میکند و UUID
به UUID دیگر آن را جایگزین میکند. شکلهای متفاوت حروف همان UUID، هویت UUID
یکسان دارند: بین دو حساب تعارض دارند ولی برای همان حساب مجازند. رمز plaintext
جدید دقیقاً تطبیق دارد و رمز قبلیِ متفاوت دیگر تطبیق ندارد. هویت مشتقشدهای که
بایتهایش عوض شدهاند دیگر resolve نمیشود؛ هویت ثابت مانند UUID در تغییر
بزرگی حروف میتواند باقی بماند. بنابراین هر تغییر متنی الزاماً تمام هویتهای
قدیمی پروتکل را باطل نمیکند و ادغام WireGuard تازهای اضافه نمیشود.
آمادهسازی و مشتقسازی یک بار پیش از قفلهای مشترک انجام میشود. ترتیب try-lock
برابر state، collection و configuration کاربر است؛ انتظار و stats lock ندارد.
از ظرفیت رزروشدهٔ index استفاده میشود و رشد/rehash یا اسکن کل کاربران رخ
نمیدهد. طول ذخیرهشدهٔ معتبر رمز قبلی زیر همین قفلها باید حداکثر ۳۲ KiB باشد؛ plaintext قبلی دوباره اسکن نمیشود؛
رکورد importشدهٔ بزرگتر بدون تغییر resource_limit میدهد. این سقف operation
است. رمز جدید ورودی همچنان اعتبارسنجی محدود دارد؛ deadline پیش و پس از precommit بررسی میشود و cleanup
جایگزینی همچنان متناسب با تخصیص قبلی کار دارد. بدنه، رشتهٔ decodeشده، staging آماده و plaintext جایگزینشده طبق مالکیت
objectها در مسیر cleanup مالک پاک میشوند؛ این تضمین شامل همهٔ بافرهای خام transport و configuration نیست.
کدهای node عبارتاند از invalid_request، conflict، not_found،
credentials_exist، busy، resource_limit، password_change_failed،
response_too_large، timeout و revision_exhausted. خطای بازگشتی operation
یعنی این فراخوانی commit نشده است. خطای parse/بودجه/crypto/قفل میتواند پیش از
بررسی تکراریبودن رخ دهد. ACK پیش از commit ساخته میشود و گمشدن آن rollback
نمیکند.
AuthenticationClient متصل با refresh عادی جدول تازه را میگیرد. کلاینت offline تا refresh معتبر با سیاست cacheشده رمز قبلی را میپذیرد؛ رمز تازه پس از آن قابل استفاده میشود. refresh بدهی و بایتهای retry دقیق batch، مشاهده/سن نخستین مصرف، ACK، projection انقضا و reservation را با ID پایدار منتقل میکند. اتصال پذیرفتهشده قطع اجباری نمیشود و حسابداری/سیاست جاری را ادامه میدهد؛ handle قدیمی همچنان reservation منتقلشده را آزاد میکند. موفقیت به معنی revocation فوری سراسری یا refresh همهٔ کلاینتها نیست.
ذخیرهٔ فوری یا تغییر primary-save history رخ نمیدهد؛ ذخیرهٔ دورهای/نهایی موجود بعداً آن را نگه میدارد و crash میتواند تغییر ذخیرهنشده را از بین ببرد. journal، رکورد درخواست پایدار یا پروتکل بازیابی crash اضافه نمیشود.
گمشدن/خرابی پاسخ، قطع یا timeout پس از dispatch نتیجه را نامعلوم میکند. ID/رمز مورد نظر را امن نگه دارید و حساب را برای مقدارهای عمومی و token تازه دوباره بخوانید. خواندن redacted نمیتواند ثابت کند کدام رمز ذخیره شده است؛ revision ممکن است با ویرایش دیگری بالا رفته باشد. صریحاً تصمیم بگیرید همان رمز دلخواه را با token تازه ارسال کنید. موفقیت ارسال دوباره فقط آن را در commit همان تراکنش برقرار میکند، نه نتیجهٔ پیشین را ثابت میکند و نه تغییر مجاز بعدی را منع میکند. در conflict دوباره بخوانید و بازبینی کنید؛ token تازه، replay خودکار یا رمز بازیابی تصادفی نسازید. ID درخواست فقط correlation است و cache نتیجهٔ retry یا میانبر replay وجود ندارد.
snapshot محدود و خطاها
خواندن روی event worker فراخواننده انجام میشود. قفلهای server، collection،
تنظیمات user و آمار به ترتیب و بدون انتظار گرفته میشوند؛ رقابت، از جمله هنگام
ذخیرهٔ موجود، busy میدهد و به معنی خرابی دیتابیس نیست. مقادیر زیر قفل بهشکل
هماهنگ کپی و قفلها پیش از rendering آزاد میشوند؛ اشارهگر user خارج نمیشود.
status کار ثابت، detail lookup با index و فهرست حداکثر کار یک صفحه دارد. بدنهٔ
نتیجهٔ حساب حداکثر ۲۵۶ KiB است و بودجه و deadlineهای gateway نیز اعمال میشوند.
شکست operation کد نتیجهٔ غیرصفر و بدنهای مانند
{"error":{"code":"busy"}} دارد؛ error بیرونی gateway برابر null میماند.
کدهای مربوط عبارتاند از invalid_request، not_found، invalid_cursor،
stale_cursor، busy، timeout، record_too_large، response_too_large و
invalid_record_encoding. خطاهای احراز هویت، مجوز و سقف پاکت encodeشده تابع
قرارداد APIServer هستند. retry و آغاز مجدد پیمایش بر عهدهٔ فراخواننده است.
متادیتای نود
metadata برگرفته از کد منبع:
| ویژگی | مقدار |
|---|---|
| node flags | kNodeFlagChainEnd | kNodeFlagNoChain |
can_have_prev | true |
can_have_next | false |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayerNone |
required_padding_left | 0 bytes |
ذخیرهٔ clean و نگهداری عملیاتی
tick دورهای clean فقط پس از checkpoint موفق process، primary قابلخواندن nonempty/regular بدون دنبالکردن link و
نبود slot تاریخچهٔ موعددار، serialization/staging را skip میکند. نخستین tick، ذخیرهٔ صریح، AddNewUser و shutdown
نهایی همچنان force میکنند. mutationهای config/stats dirty میکنند؛ replay دقیق مصرف و session enrollment نه.
commit primary dirty را پاک میکند، حتی اگر history شکست بخورد؛ slot تاریخچه تا retry موفق due میماند ولی تلاش بعدی حداقل یک دقیقه فاصله دارد. skip
فایل backup یا telemetry آخرین save را تغییر نمیدهد. status دو fact محلی database_dirty و
primary_checkpoint_valid دارد. خطای open/type/empty/access/close موفقیت نیست؛ probe محتوای JSON ویرایششده را
validate نمیکند. ویرایش فایل زنده API تغییر account نیست. ذخیره همچنان synchronous زیر write lock است.
برای audit tail خراب، همه writerها را متوقف کنید، evidence را خصوصی حفظ/قرنطینه کنید و آگاهانه فایل معتبر را انتخاب یا repair کنید. stage متروک را فقط پس از توقف همه writerها و شناسایی قطعی همان فایل خصوصی حذف کنید؛ هرگز wildcard گسترده هنگام احتمال writer زنده نزنید. stage شامل credential plaintext و نامزد recovery نیست. server secretهای producer تحت مالکیت، secret decoded Authenticate و password موقت lookup را پیش از آزادسازی با primitive مقاوم به optimizer پاک میکند؛ کپی محلی JSON/text صفحه نیز پاک میشود. APIServer و dispatcher نیز کپیهای متعلق به خود را هنگام آزادسازی نهایی پاک میکنند. config، درخت ناقص داخلی parser، بافرهای واگذارشده به handler یا transport، کپیهای OS و همه کپیهای process و فایلها در این تضمین نیستند.
اندازهٔ قابل همگامسازی و محدودیتها
هر حساب پذیرفتهشده در import، AddNewUser، UpdateUser و عملیات create/update/password API باید با metadata کامل در یک صفحهٔ sync جا بگیرد. سقف از payload برابر 16 MiB، header فریم نهبایتی و metadata واقعی با بیشترین عرض اعداد بهدست میآید؛ header بیرونی جداست. تمام scalarهای قابل خروجی، حتی آمار صفر و رشد بعدی ترافیک و اولین استفاده، reserve میشوند. این سقف، کل جدول را محدود نمیکند و ردیف شش MiB معتبر میماند.
charge محتوای JSON-escaped و وضعیت encoding پنج رشتهٔ name، password، email، notes و Allowed IPهای normalized همراه مالکیت رشته نگهداری میشود. آمادهسازی charge جایگزین را محاسبه میکند؛ transaction زیر قفل config فقط charge فیلدهای انتخابی را overlay میکند و رشتهٔ بزرگ دستنخورده یا قفل stats را نمیخواند. copy، move، import و setter نیز metadata را منتقل میکنند. generic object میتواند متن غیرقابل export داشته باشد؛ سیاست server جداست و serializer دقیق هنگام خواندن همچنان guard است.
خطای producer برابر user-too-large و API برابر user_too_large پیش از ACK/commit است و حساب، index، revision، checkpoint، dirty و فایلها تغییر نمیکنند. primary ساختاری معتبر ولی بزرگ، حتی با backup سالم startup را رد میکند؛ ID پایدار و سقف ثابت بدون متن/credential گزارش میشوند و هر دو مسیر/محتوا برای repair باقی میمانند. backup بزرگ نیز منبع restore نیست. محدودیتهای API body و old-password جدا هستند. GetUserByPassword سقف مستقل 1024 بایتی ندارد؛ تا سقف موجود request-data برابر 16 MiB و هندسهٔ envelope، با بایتهای دقیق Unicode/فاصله پذیرفته میشود؛ خالی و NUL رد میشوند.
هر batch مصرف تازه، حتی observation اولین استفاده با صفر بایت، stats_revision را یک بار افزایش میدهد؛ replay دقیق commit تازه نیست. envelopeهای producer mutation را پیش از read اجرا میکنند؛ پاسخها را با ID correlate کنید، نه ترتیب ورودی. stats.speed فقط import است و serializer فعلی آن را صادر نمیکند. precedence موجود users، containerها و aliasهای password/name تغییر نکرده است.
محدودیت index-growth مستقل است: در هندسهٔ فعلی و allowance پیشفرض create حدود 26214 کاربر و در رشد Allowed IP مرز حدود 8192 range ممکن است refusal رخ دهد؛ اندازهٔ build/value مؤثر است و ظرفیت تضمینشده نیست. pull ناموفق میتواند میان دو traversal به گزارش مصرف روی baseline دستنخورده فرصت بدهد. edit همزمان snapshot فعال را باطل نمیکند؛ بدهی ID غایب در ledger جدا حفظ میشود، نصب جدول معتبر را متوقف نمیکند و با بازگشت همان ID دوباره متصل میشود. batch نامطمئن همچنان publication را متوقف میکند مگر اینکه پاسخ دقیق عدم commit آن اعتبارسنجی شود. client crash recovery یا journal پایدار اضافه نشده است.
محدودیت parsing و snapshot همگامسازی
PushUserStats پیش از ساخت درخت JSON حداکثر عمق ۳ و ۳۶۸۷۱ مقدار lexical (شامل نام کلیدها) را میپذیرد؛ UpdateUser عمق ۴ و ۶۴ مقدار و AddNewUser عمق ۴ و ۲۵۶ مقدار دارد. آرایههای بسیار عریض پیش از allocation رد میشوند.
فایل دیتابیس میتواند یک UTF-8 BOM ابتدایی داشته باشد؛ این استثنا برای پیامهای شبکه نیست. UTF-8 نامعتبر، کلید تکراری و محتوای اضافی همچنان رد میشوند. لاگ فقط علت ثابت، offset بایت و در صورت امکان شماره رکورد و ID را ثبت میکند و متن یا credential را چاپ نمیکند. تعداد کاربران بارگذاری یا بازیابیشده نیز ثبت میشود. برای نخستین اجرا فایل db-path را با {"users":[]} بسازید؛ مسیر اصلی و backup نباید symlink یا reparse point باشند.
GetUsersPage در شروع یک snapshot تغییرناپذیر از رکوردهای encodeشده میسازد. ساخت اولیه O(N) است و قفل write را نگه میدارد؛ ویرایشهای بعدی ادامه پیمایش را باطل نمیکنند. sessionهایی که revision یکسان config و stats دارند حافظه snapshot را به اشتراک میگذارند. snapshot-max-bytes با پیشفرض 268435456، مجموع داده، offsetها، metadata و scratch ساخت را محدود میکند؛ نبود ظرفیت خطای snapshot-resource-limit میدهد. snapshot-idle-timeout-ms با پیشفرض 60000 و مقدار صحیح مثبت، lease بیکاری است؛ هر صفحه موفق آن را تمدید میکند. اعتبارسنجی نهایی، جایگزینی پیمایش، پایان session و shutdown reference را آزاد میکنند. رکوردهای encoded هنگام آخرین آزادسازی پاک امن میشوند.
revisionهای داخل صفحه مربوط به snapshot هستند؛ revisionهای envelope وضعیت زنده را نشان میدهند و ممکن است جدیدتر باشند. ساعت wall و accounting هر پاسخ تازه است. cursor به snapshot همان session تعلق دارد؛ snapshot منقضی یا جایگزینشده stale_cursor میدهد. cursor API مدیریتی list-users همچنان به revision زنده وابسته است. اگر پاسخهای چند فریم از سقف مشترک بگذرند، فریم بزرگ به خطای همبسته response-too-large تبدیل میشود و ACK تغییر قبلی حفظ میشود.
اگر batch بعدی شامل ID حذفشده باشد، سرور Error با JSON usage-accounts-missing و epoch، sequence، digest دقیق درخواست، checkpoint-sequence، checkpoint-digest و آرایه رشتهای missing-ids میدهد. این پاسخ عدم اعمال کل batch را اثبات میکند. client فقط بعد از اعتبارسنجی کامل آن میتواند attempt را آزاد و در همان sequence بازسازی کند؛ بدهی هیچ ID حذف نمیشود. replay دقیق batch قبلاً پذیرفتهشده همچنان ACK میشود.
خطای نوشتن history حداکثر هر یک دقیقه retry میشود و در فاصله آن save کامل دیتابیس تمیز تکرار نمیشود. نوشتن موفق snapshot حتی با خطای pruning slot را ثبت میکند؛ pruning با snapshot دوره بعد تکرار میشود. محدودیت هر line تعداد اتصالهای یک token را محدود نمیکند؛ listener باید محدودیت اتصال مناسب داشته باشد.