Dev Jungles @devjungles Channel on Telegram

Dev Jungles

@devjungles


Telegram канал для YouTube - https://www.youtube.com/c/DevJungles

Dev Jungles Telegram Channel (Russian)

Dev Jungles - это Telegram канал, созданный для тех, кто интересуется миром разработки программного обеспечения. Наш канал предлагает уникальные материалы и обучающие видеоролики, которые помогут вам углубить свои знания в сфере программирования. Мы сотрудничаем с крупными разработчиками и экспертами в области IT, чтобы обеспечить вам самую актуальную информацию. Канал Dev Jungles является продолжением нашего YouTube канала, где вы также можете найти множество полезных видеоуроков. Присоединяйтесь к нам, чтобы расширить свои навыки и оставаться в курсе последних технологических трендов. Подпишитесь на наш канал и начните свой путь к успеху в мире разработки ПО!

Dev Jungles

16 Jan, 17:14


Хто б як вирішував задачу:
В реляційній базі треба зберігати певні сутності.
Вони мають 5-6 спільних властивостей і енам типу.
Типів багато і вони мають 3-30 власних властивостей: іноді якась з них масив, іноді ще один об'єкт за своїми властивостями.

Вигрібати треба потім їх доволі багато (сотні, іноді тисячи, іноді десятки тисяч) і заново будови об'єкти відповідно до ієрархії.

Коротше: подія, тип, аргументи. У кожного типу свої аргументи.

Dev Jungles

08 Jan, 07:19


Зрозуміло, що про індекси писали багато. Зрозуміло, що багато чого про них розкаже і ChatGPT, чи аналогічні системи.

Тож чим буде вирізнятись моя стаття?

Вона швидше упорядковує знання про індекси в голові і дозволяє вам швидко навігуватись по ним всередені голови: тобто мати можливість ходити вверх вниз по абстракціям в процесі обдумування проблеми.

https://blog.podkolzin.consulting/article/2024-12-27.base-idea-behind-index

Індекс знань про індекси для вашої голови

P.S.
Поширте цю статтю, або будь яку іншу, що знайдете корисною тим, хто може розділити цю думку. Це буде ваш внесок у боротьбу з паршивими статтям і захист статей добрих.

Dev Jungles

28 Dec, 11:11


Для експериментів взяв найдешевший сервачок за 4$: там 512мб оперативної пам'яті і дивуюсь як там працює аж цілий ASP.NET + Nginx + postgres.

А потім згадую, що коли починав займатись розробкою, то в мене і було 512мб оперативної пам'яті: так туди влізла вінда з графічною оболонкою, я міг грати у generals zero hour чи gta vice city, чи програмувати на Visual Studio підіймаючи сайт на ASP в цілому IIS і маючи SqlServer у якості бази даних, ще й бачити результат своєї роботи в браузері.

Яким бісом це все тоді туди влазило, якщо зараз я дивуюсь із кількох процесів без графіки на віддаленому сервері? 🙈🙈🙈

Dev Jungles

27 Dec, 16:41


Доповнив свою статтю про ref struct чудовим коментарем від Дмитра. Раджу ознайомитись.

Єдине доповнення від мене:
List<string> і List<JObject> будуть мати посилання на одні й ті самі тела методів від чарівного List<__Canon> чи якось так, що застосовується для всіх ссилочних типів.

Dev Jungles

27 Dec, 16:41


Щодо питання про можливість використання ref struct в якості generic параметру:

Java generics базуються на стиранні типів (type erasure), це означає що наші T існують лише на етапі компіляції, а в рантаймі використовується звичайний object, що в свою чергу має на увазі під собою каст, а для структур ще і боксінг. Ну а як ми зрозуміли зі статті то така операція як розміщення на кучі(боксінг) не вписується в концепцію ref struct.

На відміну від Java, C#: Generics реалізовані через різні версії типів на етапі компіляції та виконання. Це означає, що компілятор створює специфічні реалізації для кожного типу, що використовує generics, з урахуванням їх конкретних параметрів типу. Наприклад, List<int> і List<string> будуть різними типами в пам’яті на етапі виконання.

Таким чином це дає нам можливість уникнути операцій приведення типів в рантаймі ну і звісно використати ref struct у якості generic параметру (звісно з певними обмеженнями).

Dev Jungles

26 Dec, 11:20


Колись дуже давно, ще в 17му році писав статтю де порівнював SQLite з іншою базою (її теж переніс у свій майбтній блог).

Зараз довелось дуже змістовно і багато попрацювати з SQLite, збудувати навколо цієї бази доволі великий продукт, тож захотілось поділитись досвідом.

Буде цікаво і тим, хто ніколи не працював з цією базою і не планує, просто для розуміння а чим так хороші ті СУБД, що працюють мережею.

https://blog.podkolzin.consulting/#/article/2024-11-26.SQLite-Expirience-Short-Article

Dev Jungles

23 Dec, 17:05


ref struct - Що це таке і що змінилось у C# 13

В цьому стрімі згадав про ref struct і казав, що він змінився.
Написав невеличку статтю про те що це таке, навіщо треба і як змінилось.

Опублікував його в тому, що колись стане моїм блогом(є одна ідея як це гарно зробити), але поки це просто пара веб-сторінок, що аж ніяк не псує задоволення від перегляду конкретної статті.

https://blog.podkolzin.consulting/#/article/2024-12-12.ref-struct-CSharp13-and-before

Dev Jungles

21 Dec, 11:55


Є баги, що повторюються від продукту до продукту.
Іноді це зумовлено тим що в різних продуктах використовуються одні й ті самі базові технології і цим можна пояснити однаковий баг в різних продуктах.

Іноді є баги, що не залежать від технології: вони повторюються в різних продуктах на різних технологіях. Зазвичай причина їх появи доволі фундаментальна. Часто зрозуміла.

І є от такий баг: майже в усіх плеєрах потокового відео неможливо поставити на паузу, якщо під час відтворення почалась підвантаження(частіше за все ми бачимо "крутілку").
Я би зрозумів якби в цей час був фріз воно UI, але ні: ми навчились виносити IO так щоб він не впливав на UI.
Може там і є щось умовно фундаментальне, але я не розумію, чому не покласти в якусь чергу подій повідомлення про те, що треба поставити плеєр на паузу як тільки дованиажиться наступний кадр/секунда перед тим як його/її відтворювати. Чому так не зроблено і чому ця проблема виникає на мобільних пристроях, в браузерах на натуральних комп'ютерах і на телевізорах мені не зрозуміло.

В когось є відповідь чи хоча б думки?

Dev Jungles

20 Dec, 15:26


Я хто який HTTP метод використовує, коли стан системи змінювати не треба, але потрібно передати BODY запиту?

У GET body тулити якось не прийнято.

Dev Jungles

09 Dec, 16:40


Clean/Onion/Hexagonal/DDD архітектури.

В них складно розібратися, особливо коли все ще бачимо скільки лайна відбувається навіть з класичною трьохслойкою, що доволі надійно є в кожній голові кожного розрабоника (чи має бути в голові будь якого розробника).

Розділяти код що працює з інфраструктурою (базою і зовнішніми сервісами) і самою бізнес логікою є чудовою ідеєю.
Ідея розділяти логіку і presentation є чудовою і її фантастично не вистачало у часи WindowsForms, WebForms і купи проектів на класичному ASP.NET.

Якщо ви будете сумлінно слідувати цим підходам, то нічого аж занадто поганого з вами не трапиться.

Наступним кроком після сумлінного слідування буде ще й виправлення існуючого коду, що не слідує цьому підходу, коли ви в ньому щось змінюєте дуже повільним ітераційним рефакторингом. Тим що не потребує окремих задач чи планування, чи апрувів бюджету.

Звісно про трьохслойну архітектуру можна казати тільки якщо код є нормальним на рівні простої структури. Якщо ні, то це те що треба виправляти при першому доторканні до цього коду: кращі назви змінних, виділення методів на рівні тих самих класів. Дуже прості, дуже безпечні зміни.

Але що далі?
Яка єдина ідея, що може змусити рухатись далі і отримати ще кращу кодову базу?

Кращу в плані, що буде легше вносити зміни і верифікувати їх.

Такою простою ідеєю буде створення класів, що не мають залежностей від інфраструктури і просто інкапсулють логіку. Частіше за все в C#/.NET вони будуть виходити синхронними. Це не є ані необхідною, ані достатньою умовою, але може бути непоганою еврітикою. В нас їх багато в розробці, тож не жахайтесь.

Тож який алгоритм дій?
- Поки все погано зі структурою коду, покращуйте її.
- Коли зі структурою порядок, то виділяйте і ізолюйте слої
- Коли з ними порядок, то спробуйте виокримити логіку у щось окреме, що не буде залежати від структури: часто це будуть класи де не буде залежностей, або ж будуть залежності лише від таких класів, що не залежать від інфраструктури. Майже завжди це будуть синхронні методи і часто вони не будуть мати side-effects, тож будуть чистими функціями.

#WrittenInTheJungle #Misc

Dev Jungles

08 Dec, 15:00


Які б технології ви обрали для швидкого й малого проєкту, що все ж таки має деяку складну логіку й не може бути зібраний геть без коду?


Не буде ані навантаження, ані великих обсягів даних, ані дуже складної бізнес-логіки.

Зрозуміло, що народ тут у мене любить мене й .NET, але ж я не є технологією, тож певно оберуть .NET.
Хоча, з іншого боку, навіть серед дотнетчиків рівня лідів є думка, що під такі вимоги краще підійшов би Python чи NodeJS.
Мовляв, .NET — це для великого й ентерпрайзного.

У часи великого .NET Framework загалом так і було. Шукати віндовий хостинг для якоїсь малої "іграшки" було непросто, у той час як підняти щось на PHP — дуже просто.

Але часи змінилися.
Змінився як сам .NET, так і те, де ми можемо його розгортати.

Тож я, коли замислився над питанням вище, не дуже довго думаючи, обрав .NET.
Minimal APIs (тільки, благаю, не загортайте кожен ендпойнт в окремий клас і не використовуйте для цього окремі бібліотеки) + record дозволяють вмістити на екрані одного монітора API й доменну модель такого невеличкого проєкту. На другий екран якраз влізуть ті методи, які повинні щось рахувати чи звертатися до якихось ThirdParty за даними.
Тобто весь проєкт складатиметься з двох файлів: csproj + cs, а обсяг коду буде приблизно таким, як на Python чи NodeJS.

Що я отримаю від C#/.NET у цьому випадку?

Я писатиму мовою C#, яку знаю настільки добре, що можу писати навіть без інтернету й IDE. Це не означає, що я так буду робити, але це шалено скорочує час на розробку.
Я використовуватиму .NET і знайомі бібліотеки навколо: не доведеться гуглити, як вибратися з якоїсь неочікуваної ситуації, чи шукати дурні помилки в конфігураціях. Я це вже робив сотню разів.
Я знаю з десяток способів налаштувати збірку й деплой залежно від інструментів: можу почати з ручного деплою через SSH або просто скопіювати GitHub pipeline із попереднього свого проєкту.

Тож ризики мінімальні, а часу буде витрачено значно менше, ніж на будь-якій іншій технології.
Це не означає, що .NET якийсь особливий, просто він достатньо крутий, щоб не створити проблем, а решту закриє те, що я його добре знаю. Для когось це буде правдою для Java, PHP, Ruby тощо.
#WrittenInTheJungle #Misc

Dev Jungles

25 Nov, 07:11


Є такий хлопчина в дотНеті, як Влад Фурдак. Він то там, то сям виступає, а ще будує купу ком'юніті навколо дотНету. Сам він Microsoft MVP -- така крута винагорода для тих, кого майкрософт помітив, тому що вони робили багато розповідаючи про технології.

В нього є два ком'юніті(насправді три, але одне ну дууже секретне):
https://t.me/+TvpQn1N6oPV256S8 - для всіх
https://t.me/dotnetboost - для тих, хто ще не просякнутий дотНетом наскрізь.

Якщо пройдете ці два ком'юніті до кінця, то можливо Влад, додасть вас і до третього, але це не точно 😜

Dev Jungles

22 Nov, 07:03


Проект середнього розміру (солюшн на 35 збірок) оновив з .net 8 до .net 9 + усі основні залежності.
Хвилин 30-40 зайняло, через те що nuget підтуплював, можна швидше було б.
Код міняти не довелось, нічого схоже не зломалось.

Що там у вас? Все ще сидите на справжньому LTS? (.NET Framework 3.5, що підтримується до 01.2029)

Dev Jungles

21 Nov, 08:49


Цікавий баг зловив з swagger генерацією (як там цей пакет називається правильно для asp.net).

Часто використовую анонімні типи як результат у MinimalAPI.
І от стався конфлікт генерованих імен для них: оце AnonymousType<....

Ну замінив пару типів на не анонимні і все запрацювало, але впавший swagger був великою несподіванкою.

Dev Jungles

12 Nov, 13:56


А хтось в курсі, чого MS не робить LINQ to Async Enumerable? Там щось фундаментальне чи ідеологічне, або просто відкладають поки?

Dev Jungles

01 Nov, 09:34


ICollection<int> a = [];
IEnumerable<int> b = [];


Стало цікаво які типи будуть. Перевірив, виявилось логічно.

Хто без підглядання скаже?

Dev Jungles

27 Oct, 11:55


Етер через 5 хвилин
https://youtube.com/live/1QdMMVo4FPc

І от сюди можна накидати питаннячок:
https://app.sli.do/event/pvFhRzC6UzVDx3cHmUaemp

Dev Jungles

26 Oct, 09:05


Зібрав трохи прикольної інфи про Frozen і не тільки колекції в дотНеті, бо згорів з усіх під копирку статей в інтернеті, що не дали відповідей на мої питання.

Завтра поділюсь.

Dev Jungles

26 Oct, 08:11


Нагадую що завтра стрім:
https://youtube.com/live/1QdMMVo4FPc

І що от сюди можна накидати питаннячок:
https://app.sli.do/event/pvFhRzC6UzVDx3cHmUaemp

Dev Jungles

24 Oct, 11:16


Неділя: 14:00 за Київським часом
https://youtube.com/live/1QdMMVo4FPc

Привіт! Давно не проводив стріми і геть не знаю, коли наступний: тож долучайтесь до цього обов'язково.
Закидайте свої питання заздалегідь в Slido: - і про дотНет і про ринок і про якісь ще технології. Коротше так, щоб було цікаво і мені і вам!

Як завжди приготую технічну штуку на затравку хвилин на 30, потім поспілкуємось трошки про гроші: є ідея для цікавої і мені і вам інтеграції, тим паче при такому стані на ринку, ну і потім буду відповідати на питання.

Make DevJungles Great Again


Тож, залітайте! Тим паче стрімлю зараз не так часто як раніше)

Dev Jungles

22 Oct, 16:31


Ідемпотентність

Складне словце, що мене спитали років п'ять тому на співбесіді.

На практиці це всього лише:
Це така властивість операції, що приводе систему в однаковий стан вне залежності від того була операція виконана один чи кілька разів.

Як і більшість визначень воно абсолютно точне і майже повністю непригодне до використання.

Давайте думати що нам може давати мислення про операції в системі як ідемпотентні і не ідемпотентні?

Одразу приходе в голову можливість ретраїв. Будь яку ідемпотентну операцію безпечно повторювати, якщо в нас немає впевненості в її успішному виконанні.
Більше того: операцію точно не можна безпечно повторювати, якщо вона не є ідемпотентною.

Так подумали і виробники купи мережевого заліза і софту.
В HTTP є методи: GET, POST, PUT, PATCH, DELETE

GET не має змінювати стан системи, а такі штуки ідемпотентні за замовчанням. Хоча ви легко можете це зламати, зав'язавши на це, наприклад, лічильник переглядів.

GET запити
можуть бути повторени браузером чи, навіть якоюсь з залізяк між вашим
комп'ютером і сервером: вишки зв'язку, супутники старлінк,
маршрутезатори провайдеру, чи маршрутезатори в датацентрі.

PUT і DELETE теж рахуються ідемпотентними, хоча це дуже легко зломати в реалізації.

Якщо ваш DELETE ендпойнд
видає помилки при неможливості знайти елемент для видалення він не буде
ідемпотентним: перший раз він видалить елемент і поверне успіх, а на
наступний впаде. Але щось в мережі може випадково повторити запит і ви
будете довго намагатись зрозуміти чому в вас в логах помилки. Не робіть
так.

Ключова відмінність PUT і PATCH як раз в ідемпотентності.
Тож PUT це щось типу x = 10. Таку зміну скільки не повторюй система все одно приходе до одного і того самого стану.
А PATCH це вже щось типу x++, котрий повторювати може бути небезпечно.

Дуже часто буває корисним зробити метод POST також ідемпотентним, вже не через мережеве залізо і софт. Нажаль це не завжди просто.

Якщо у вас є обмеження в системі на однакові об'єкти, то ви можете використовувати хеш від об'єкту
про хеші багато писав у попередніх постах) для перевірки присутності
об'єкту в таблиці чи колекції: якщо він там вже є, то повертати успіх
вне залежності від того коли і чого він там був створений. Такий підхід зробе POST ідемпотентним.

Але що робити, якщо в системі можуть бути однакові об'єкти?
Це ускладнює задачу, але не робить її неможливою.
В цілому все зводиться до того, щоб зробити з однакових об'єктів унікальні, а потім заборонити повтор унікальних об'єктів.

Як саме?
Додавши до об'єкта якусь унікальну випадкову послідовність, або дата до микросекунд.
Якщо не буде впевненості, що запит був оброблений успішно, то його можна буде повторити.

Якщо запит таки не був оброблений, то сервер його обробить.
Якщо вже був, то на сервері спрацює перевірка що такий об'єкт вже є і він просто нічого не стане робити.

Іноді це такою унікальною послідовністю можуть бути навіть ID, якщо є
можливість генерувати їх на клієнті: це буває коли викорстовуються GUID
чи щось подібне.

Але ідемпотентність не тільки про мережу і не надійне середовище. Іноді це просто спрощує код. В .NET правильною реалізацією Dispose вважається
ідемпотентна. Це дозволяє уникнути додаткових перевірок на володіння в
ієрархіях об'єктів і менше думати про порядок знищення об'єктів.

#WrittenInTheJungle #Misc

Dev Jungles

18 Oct, 16:41


NULL as Billion Dollar Mistake: чи можливо було уникнути?

NULL було дуже легко додати виходячи з архітектури програмного забезбечення, тож його додали і це стало помилкою, що коштувала індустріям мільярди.

Така одна з версій подій.
Не суть чи було так, чи ні, краще подумати про те:
- А чи могло бути інакше?
- Як це могло б бути?
- Чи було б це краще?

Коротко про NULL.
NULL це таке спеціальне значення, що є аналогом нічого.
Навіщо нам спеціальне значення для "нічого"? Бо є принципова різниця у відповідях на питання: "Скільки грошей в гаманці?". Відповідь "не знаю" дуже відрізняється від відповіді "нуль". А ми можемо хотіти створювати алгоритми з урахуванням цього: в одному випадку, наприклад, перерахувати, а в іншому вигнати з магазину. Ну, наприклад.

Для більш складних об'єктів різниця може бути ще більш значущою. Ми намагаємось з'ясувати кількість грошей в гаманці у людини, а в людини навіть немає гаманця, або навіть немає самої людини.

Якщо додати до цьго колекції, то все стає ще складнішим: відсутність колекції, тобто NULL, пуста колекція, колекція з NULL замість людини, людина з NULL замість гаманця і людина гроші в гаманці котрої ми ще не рахували - тобто теж NULL.

Звучить як халепа. Чи не так?
І от що було б, якби такого особливого значення не було? Як це могло б бути?

Деякі мови зараз, намагаються проектувати саме таким чином. АЛе в кожній з них все одно з'являється щось схоже на той саме NULL. Бо нам він потрібен на рівні СУТІ РЕЧЕЙ!

Маючи таке особливе значення, ще можливо надресерувати компілятор таким чином, щоб ті значення, що допускають NULL було неможливо використати без попередньої перевірки.

Непоганий варіант.

Але зазвичай в нас не вистачає клепки, сил та часу в кожному місці, в такій перевірці написати щось дійсно розумне -- видати гарну змістовну помилку, що допоможе користувачу, або ж повернути якесь значення за замовчанням.
Тобто: цей підхід може частково працювати, але повністю проблему не вирішує.

А що якщо геть позбутись цих NULL values і всюди робити якісь об'єкти за замовчанням?
Цей підхід теж буде до певної міри працювати, аж до того часу як ви замість NullReferenceException отримаєте некоретну поведінку програми. Можливо ще й не сразу, а далеко в проді для багатьох користувачів.

Неправильну поведінку завжди важче помітити аніж crash програми. Це легше пропустити на усіх етапах тестування. Така помилка може виявитись значно дорожчою.

Проблема NullReferenceException у тому, що воно виникає пізніше ніж його реальна причина.
NULL встановлюється в одному місці, а програма падає значно пізніше. І де саме встановлюється NULL треба знайти.
Зі значенням за замовчанням ще складніше, бо до того, щоб зрозуміти, що значення не встановлюється треба ще відрізнити значення за замовчанням, від реального.

Тож, я не впевнений, що без NULL життя було б кращим.

#WrittenInTheJungle #Misc

Dev Jungles

12 Oct, 10:50


На першому приближені, перед тим як починати будувати програму зручно проаналізувати те що має вийти з точки зору inputs and outputs.
Тоді сам застосунок буде чорним ящиком посередені.

Такий аналіз гарно помагає і на етапі перших думок про архітектуру і про дизайн і плануванні бази.

Є різні способи як рухатись далі. Бо, насправді, будь який програмний продукт вже набагто складніший за просту чисту функцію, що ідеально описується входами і виходами.

Іноді, може бути зручно розбити все на кілька категорій:
- Як представити данні, щоб користувачу їх було зручно вводити? (Edit ViewModel)
Тобто нам як програмістам зручно відобразити інтерфейс для їх вводу і заповнити його базовими значеннями.
- Як представити данні, щоб їх було зручно відображати? (Presentation ViewModel)
Іноді це може бути те саме що і ввод, а іноді геть інше. До того ж іноді нам треба відображати множини об'єктів, якось між ними навігуватись, будувати агрегати чи проекції.
- Як представити данні, щоб на їх осонові було зручно виконувати обчислення? (Model)
Бувають і суто інформаційні застосунки, де цього майже і не треба, а буває і навпаки.
- Як представити данні, щоб їх було зручно зберігати у базі (чи деінде)? (Entity)

Тоді немало коду, іноді навіть не найпростішого, доведеться написати для перетворення одних сутностей в інші.
Але я кілька разів значно знижував складність кожної з частин(ввод, відображення, обчислення, збереження) застосунку просто завдяки вдалому проектуванню цих об'єктів, а не спробам проводити обчислення на тих самих сутностях, що і бачить користувач, або тих самих, що прийшли з базі.

Данні можуть бути одні й ті самі по своїй суті, а от їх вигляд з точки зору класів і колекцій може бути геть різним для всіх цих випадків.

Якщо доводилось вдаватись до цих трюків, то мапери рідко ставали в нагоді, або ж головної болі з ними було більше ніж тої, що вони могли позбавити.

#WrittenInTheJungle #Architecture

Dev Jungles

10 Oct, 16:19


- Слухай, а можеш мені розказати як працює цифровий підпис. Там теж щось з хешами пов'язано?
- Так, але там додається ще й асиметрична криптографія.
- А це що ще за звір?
- Симетрична криптографія, це коли одним і тим самим ключем і шифруємо і розшифровуємо, а асиметрична, це коли ключі для шифровки і розшифровки різні.
- Щось я вже тут перестав розуміти.
- Із паршивого, що в мене навіть немає простого прикладу як це пояснити. Там вже іде хитріша математика. Тож давай зосередемося на властивостях: в асиметрічній криптографії одним ключем шифруємо, іншим розшифровуємо.
- Здається запом'ятав.
- Добре. А тепер уяви, що всі знають ключ яким можна щось розшифрувати, а лише ти знаєш ключ, котрим шифрувати.
- Звучить дико, якщо чесно, ало що нам це дає?
- А те, що якщо нам приходить шифрований документ і він розшифровується саме твоїм ключем, то значить його зашифрував ти.
- Точно. І правда. Але ж от мені надсилають підписані документи і я їх можу бачити, вони геть не зашифровані.
- Все вірно. Бо сам документ може бути і відкритий, просто до нього дописали об'єкт додатковий - цифровий підпис.
- А якщо цей підпис скопіюють до іншого документу... Це ж ненадійно.
- Вгадай що може це забороти?
- Хеші?
- Саме вони.
- Але як?
- Давай розглянемо цифровий підпис як об'єкт: там є хеш від документу котрий ми підписуємо, щоб не можливо було підсунути інший документ до того ж самого підпису. Цей хеш зашифрован приватним ключем - тим самим, що відомий лише одній людині.
- Чорт, а це прямо красиво.

#WrittenInTheJungles #HashSeries

Dev Jungles

09 Oct, 16:20


- Да, майже. Зараз буде цікаве. Коли додаємо ще один елемент ми обов'язково вказуємо хеш попередника, потім власне елемент і потім хеш від усього цього.
- Так. І що нам це дає?
- А те, що якщо хоч один елемент змінюється десь внизу, то летить все до біса. І ми можемо це перевірити.
- Все ще не розумію.
- Ну от зібрали ми колекцію з таких елементів: якісь корисні данні, хеш попередника і хеш від корисних даних і хеша попередника. Зібрали масив з сотні таких записів.
- Так
- І тепер уявляємо, що якийсь зловмисник змінює щось в корисних даних двадцятого елементу. Ми можемо перевірити весь ланцюжок і знайти це!
- А, це тому що хеш не зійдеться?
- Саме так. Ми перевіримо хеші елементів з першого по 19 і все буде чудово. А потім обчислемо хеш від корисних даних і хеша попередника 20го і він не зійдеться з тим, що написано в його хеші.
- А якщо зловмисник змінив і його?
- Добре, тоді звалимось на наступній перевірці: у 21го елемента у якості батьківського хеша буде старий хеш.
- Стоп. Якщо ми вважаємо, що зловмисник може переписувати данні якимось чином, то він же може переписати і його?
- Так. При чому йому доведеться змінити кожен елемент після.
- Простий цикл має впоратись. Хіба ні?
- Десь так, але от... Уяви, що ти робиш Code Review і бачиш одну маленьку зміну: таке можна пропустити, можна непомітити, а от якщо змін багато, то може і не кожна з них захопу увагу, але сумарна увага точно буде більше.
- І все ж таки де тут безпека?
- Звісно, якщо ми одні і в нас є повний контроль над даними і ми самі собі зловмисник, то це фундаментально неможливо захистити. Але згадуємо що база розподілена: це значить, що тобі доведеться змусити усіх прийняти не одну твою маленьку зміну, а величезну купу змін. І обгрунутвати їх для них.
- А як це робиться?
- Ух. Це точно вже не сьогодні.

#WrittenInTheJungles #HashSeries

Dev Jungles

09 Oct, 16:19


- Ти казав, що хеші, ще використовуються якось в біткойні. Так?
- Я сказав не так. Хеші використовуються в блокчейні.
- В чому різниця?
- Блокчейн це структура даних. Біткойн програма(а також ще валюта), що викорстовує цю структуру даних. Миж не називаємо усі програми масивами тільки тому, що вони там використовуються?
- Зрозумів. Так а хеші так навіщо?
- Я колись дуже докладно це розбирав в одному з відео, але тобі ще може бути зарано таке дивитись, тож давай коротенько
- А це обідно було...
- Проігнорую. Так от. Є структури даних - це такі алгоритми, що описують як ці данні зберігати. Те що виходить в результаті має свої властивості: сильні, слабки сторони, обмеження, тощо. Ті самі хештаблиці, що ми розбирали до цього, наприклад, які мали властивості?
- Швидкий пошук по ключу, швидка вставка, швидке видалення і не супер по пам'яті.
- Так. А тут інша структура. І її властивостю буде можливість виявити підміну даних.
- І навіщо це може бути потрібно?
- Для того щоб збудувати надійну розподілену базу даних в середовищі де те не знаєш кому довіряти.
- Вау
- Тут звісно згадуємо біткойн, як валюту без центробанка і взагалі без банка хто б її обслуговував.
- Так і де тут хеші?
- А дуже просто: я ж вже сказав, що це структура даних, тобто так чи інакше колекція елементів з можливістю туди щось додати.
- Так
- Додаючи елемент ми додаємо не лише його, а ще й його хеш.
- Як в TCP протоколі?

Dev Jungles

08 Oct, 16:40


Більше двох років тому, вже писав тут про хеші і їх засолювання.

- А те що ти казав, що хеші дозволяють і в швидкість і безпеку це і були контрольні суми в пакетах в TCP чи є ще щось?
- Так є і ще багато трюків.
- Наприклад?
- Давай почнемо з чогось простого: пом'ятаєш архівчики з паролями?
- Так.
- Я завжди ломав голову як вони роблять безпечно функцію: ругнутись модальним вікном на невірний пароль.
- ..., - (Замислився)
- Ну да, мені прямо спокою не давало: бо якщо його дописати в сам файл, то його можна буде вскрити. Якщо зашифрувати, то як дізнатись, що це він, а не мусор?
- Мабуть можна було б дописувати якусь кодову фразу в шифрований зміст і після розшифровки перевіряти її наявність: якщо є, то все добре.
- Доречі непоганий варіант, але має один недолік. Якщо архів великий, то не швидко ти зможеш ругнутись на те що пароль не вірний: аж після всієї розшифровки.
- Можна її складати десь в початок
- А знаєш, я тут зрозумів, що варіант твій паршивий: це знижує криптостікість. Якщо ти знаєш що зашифровано, то це легше взломати. Але і це не єдина проблема.
- А яка ще?
- Багато з сучасних хешів перемішують вмсіст інформації, що шифрують: тож можуть або розшифрувати повністю, або ніяк. Можна, звичайно бити на шматки, але тоді повертаємось до криптостікості.
- Добре, а як тоді?
- А ми до шифротексту(так називають результат шифрування) додаємо ще й хеш паролю(сам хеш у відкритому вигляді).
- Вау. І тоді ми можемо звірити хеш введеного паролю, доволі швидко і якщо він правильний, то іти розшифровувати, а якщо ні, то йти показати це користувачу.
- Саме так, але і це має свої проблеми з безпекою
- ..., - (замислився надовго), - Які?
- А те, що гарних хеш функцій в нас небагато і робити їх важко, паролей хоч і багато, але не настільки, щоб разок не сісти і не свторити словник(да-да саме словник як хештаблицю, де ключем буде хеш, а значенням пароль.) з ними усіма (ті що люди релально використовували). Тоді: якщо пароль один із таких, то ти дуже швидко це з'ясуємо і вскриємо
- Від цього вже не захиститись? Тільки унікальні паролі всюди?
- Да чого ж. Трішки можна. Це називається підсолювання хешів.
- Кулінарно...
- Диви: ми до паролю додаємо унікальну випадкову сіль(просто якийсь не дуже довгий унікальний рядок) і хешуємо вже з нею.
- Вау.
- А тоді, звісно паролі що люди колись використовували перебрати знаючи сіль можна, але ж це під кожен архів треба робити окрему таблицю.
- Це все цікаво, але щось від тебе нафталіном тхне. Хто ще використовує шифровані архіви сьогодні?
- Біс його хто, але підхід той саме і з паролями користувачів на сайтах: не зберігай пароль, а зберігай хеш. І не забудь кожен унікально підсолити.
- Вау втретє! Тоді, навіть якщо зловмисник заволодіє базою, то він не зможе дістати з неї паролі
- Зможе, якщо пароль не дуже довгий, або не дуже унікальний: тобто суттєво зменьшить кількість можливих варіантів. Але ще: за рахунок того, що сіль унікальна для кожного користувача йому доведеть витрачати достобіса потужної потужності на кожного з користувачів. Не вийде один раз зробити таблицю хешей популярних паролей вже з сіллю і нею вскрити усіх, щоб потім побігти на інші популярні ресурсі очікуючі, що у людини всюди один і той саме пароль.
(Ще трохи тут)

#WrittenInTheJungle #HashSeries

Якщо комусь це цікаво чи корисно, то закиньте йому чи їй цей допис. Мені буде приємно.

Dev Jungles

07 Oct, 10:29


Як робиться надійність за допомогою хешів?

- Я второпав як хеші допомогають покращувати швидкість застосунку до шалених значень, але жеруть порядком так пам'яті, побачив як зробити таку структуру, що може жерти рівно стільки пам'яті скільки ти їй даси, але ти казав, що хеші ще якось використовуються в безпеці і надійності?
- Так.
- Але ж вони самі по собі не дуже надйні. Колізії і всі діла.
- Так, проте це все одно може покращити надійність, коли її небагато. От уяви, що в нас є швидкий, але паршивий за якістю канал зв'язку.
- Що значить: "паршивий за якістю"?
- Дані або губляться зовсім, або частково, або просто псуються: тобто частина даних ті(вірне), а частина інші(невірні). І треба налагодити надійний зв'язок по такому каналу.
- Хіба це можливо?
- Цілком. Нам треба лише виявляти ті випадки, коли дані загубились чи зіпсувались і мати можливість запросити втрачені заново.
- І тут нам стануть в нагоді хеші?
- Можна і без них звісно. Давай навіть спробуємо. От ти відправляєш повідомлення, але ти не впевнений, що воно не зіпсується. Тому ти береш і пишеш одне й те саме повідомлення двічі. Якщо перше не співпадає з другим, то значить щось не так. Яке з них вірне не з'ясувати, тож треба запитувати все заново.
- Ага. Зрозумів. Подвійне навантаження з якого лише половина корисного. А значить замість дублювання повідомлення можна дописати до повідомлення хеш. Потім обчислити хеш повідомлення, що прийшло і порівняти з тим що було дописане.
- Саме так. Якщо не співпало, то просимо повторити. Є звісно імовірність колізії, але важко уявити, що випадково зламене повідомлення зламається саме таким чином, щоб дати колізію хешу. Ну звісно, окрім випадків, коли наша хеш функція дуже паршива і на всі, чи майже всі вхідні параметри дає один і той саме результат. В цілому: ми на пів дороги до винайдення TCP.
- Чому на пів?
- Бо ми ще не вирішили проблеми повної втрати повідомлення. Коли немає з чим порівнювати.
- Щось не можу придумати як тут може бути внагоді хеш.
- Насправді може і бути: наприклад ми будемо дописувати до пакету хеш попереднього повідомлення. Потім перевіряти чи те повідомлення, що ми вважаємо останнім має такий саме хеш. Але то вже майже якийсь blockchain виходить. В TCP воно вирішено значно простіше.
- І як?
- Да просто нумеруються пакети да і все.
- Вау, а це дійсно просто.
- Плюс коли ти отримуєш пакет, ти ще відправляєш один назад з номером того пакету що отримав. Якщо з номерами щось не те, то частина пакетів буде відправлена заново.
- А що як наступного пакету так і не буде?
- Поперше такого не може бути: бо ти все одно змушений надіслати підтвердження в отриманні на кожний пакет. А по друге, часто щоб зробити протокол ще надійнішим додають ще механізм серцебиття (heartbeat).
- Це що таке?
- Да просто пусті повідомлення з цими номерами, що мають відправлятись протягом певного часу. Якщо такого немає в зазначений час і ще якийсь час додатково, то значить з'єднання протухло: його можна викидати і створювати нове.
- Про час не дуже зрозумів.
- Приклад: обидві сторони відправляють пакети серцебиття щосекунди, але є там затримки мережі і таке інше, тож якщо рівно за секунду пакету немає, то ще не факт, що все пагано: але якщо пакету немає вже 5 секунд, то точно все пагано.
- Второпав.

#WrittenInTheJungle #HashSeries

Якщо комусь це цікаво чи корисно, то закиньте йому чи їй цей допис. Мені буде приємно.

Dev Jungles

06 Oct, 08:21


- Слухай, а мені сподобався трюк із масивом, де ми його зменьшили з двох мільярдів до одного. А за потреби на скільки завгодно.
- Так, у двічі то було тільки для прикладу, так-то ми можемо обмежитись і доволі невеликим масивом, а збільшувати лише за потреби. Там коли заповнився якийсь відсоток значень.
- Так, так, але я не про те. Ми ж за допомогою хешу і всього цього змогли навчитись дуже швидко відповідати, що елементу немає в колекції. При чому з абсолютною точністю. А от вже відповідь про наявність вже не настільки точна. Вірно?
- Саме так, тому для того, щоб не було помилково-істиних відповідей ми ще порівнюємо елементи.
- До діла. Мені поклали веб сервак, тупою DOS атакою. В мене є доступ до ресурсів по GUID. Раніше я бігав в базу за кожним і якщо його немає, то повертав 404.
- Дай вгадаю: зловмисники просто почали випадково генерувати GUID і це поклало базу, хоча жодний запит і не був по ресурс, що був в наявності? Тобто завжди були лише 404.
- Так. І я, поспілкувавшись оці два рази з тобою про хеші, подумав: а давай я їх складу всі у HashSet і буду швиденько віддавати 404, якщо GUID немає в HashSet, а якщо є, то вже тоді бігти в базу по ресурс.
- Звучить непогано, але?..
- В мене доволі кволий сервер, бо це мей pet проект і мені не вистачило оперативи на цей HashSet.
- І є якась шалена ідея. От прямо по очах бачу. Так?
- Ага. Виділяємо масив фіксованого розміру. Все по замовчуванню FALSE. При додаванні елементу рахуємо хеш і складаємо TRUE по індексу:
hash % array.Length

- Тоді, коли приходе запит, то обраховуємо хеш від GUID, беремо залишок від ділення і перевіряємо чи стоїть TRUE у цій позиції.
- Але можуть бути колізії, тож TRUE нам ще не гарантує наявність, але FALSE гарантує відсутність.
- Якщо TRUE, то можна вже сходити в базу і перевірити чи справді є такий елемент. Але купу запитів ми вже відсіємо. Це значно здорожчить атаку, якщо хтось ще спробує покласти твій сервіс, бо ти відсіїш більшість запитів ще до походу у базу.
- І найкрутіше: я можу строго зафіксувати розмір масиву. Не буде ані алокацій, ані непередбачуваності в плані розміру.
- Тебе часом не Блум звати?

Гугліть: Фільтр Блума для більшої кількості деталей.

#WrittenInTheJungle #HashSeries

P.S. Як модифікувати атаку таким чином, щоб вона все одно поклала сервіс? Пишіть у коментах. Може футболку, комусь подарую за кльову відповідь. В мене одна ідея є.

Dev Jungles

05 Oct, 10:55


Завдяки чому все може працювати швидко? (HashSet, HashTable, Dictionary)

- Так до чого хеші і перфоменс застосунка?!
- Спрва в тому, що в нас фундаментально усього два способи зробити щось швидше: або робити меньше обчислень, хоча б у більшості випадків, або ж залучити більше потужності для обчислень. Більше потужності, то краще залізо, більше грошей клауду за крутішу вірталку, чи навіть спробувати витиснути більше з того що є завдяки обробці у кілька потоків.
- Все ще не може второпати до чого тут хеші? Залізо додаткове вони не дадуть, а роботи наче як навпаки додають.
- Не без цього. Можна, якщо знати куди бити, навіть ушатати застосунок і покласти сервери когось, хто тобі не подобається, але здебільшого ми все ж таки виграємо.
- Як?
- Пам'ятаєш властивіть хеш функції: якщо хеш не співпав, то об'єкти точно різні, а якщо співпав, то імовірно однакові.
- Так. І?
- Тобто порівнявши хеші ми можемо уникнути порівняння самих об'єктів якщо вони не співпали.
- Це вже щось... Деякі об'єкти порівнювати довго і шукати по хешам було б значно швидше.
- Саме так. Навіть якщо ми просто додамо до об'єкту хеш як поле і будемо видавати FALSE, якщо вони не співпадають, то ми вже значно виграємо на складних об'єктах з купою полів.
- Звучить не погано, але для них і хеш треба прорахувати відносно всіх цих полів, тож це теж часу займе добряче.
- Ну звісно: якщо тобі треба пошукати усього лише пару разів, то це точно не стане в нагоді. Але уяви, що в тебе повно тих об'єктів і шукати по ним доводеться часто.
- Не погано, але все одно наче не так і багато де застосуєш: часто треба шукати по імейлу, логіну чи якомусь ID, що порівнюються може і довже за хеші, але не суттєво. А тут ще й треба десь заздалегіть всі хеші обрахувати, зберігати, та слідкувати, щоб додавати туди нові об'єкти та видаляти старі.
- Саме так. Все вірно, але є ще один трюк, що вирішує усе. Нам треба розкласти об'єкти, разом із хешами особливим чином, щоб ще значно скоротити пошук. Тобто робити суттєво меньше.
- Як саме розкласти?
- Давай уявимо, що в нас хеш це інт. Для простоти скажемо, що він не може мати від'ємних значень. Тобто від 0 і до 2х з гаком мільярдів. А тепер трохи поїдемо дахом і зробимо масив на ті самі два з гаком мільярди елементів. Всі за замовчуванням пусті, але обраховуючі хеш ми використаємо його як індекс у цьому масиві і покладемо сам елемент на його місто.
- І тоді ми можемо перевірити наявність елементу у колекції просто перевіривши відповидний елемент масиву на пустоту. Умовно на null. Це буде швидко, але що робити з колізіями?
- Щоб не сказати на алемент котрого насправді нема, що він є ми таки збережемо оригінальний елемент і порівняємо з ним перед тим як остаточно повернути TRUE.
- Ага. І це буде усього лише одне порівняння вне залежності від кількості елементів в масиві.
- Нууу... Майже. Колізії несуть дві загрози:
1. Можливість видати помилково TRUE, це ми вирішуємо збереженням елементу і порівнянням при пошуку.
2. Нам може буде потрібно зберігти два різних елементи з однаковим хешем.
- Ох
- Ага. Тому ми змушені зберігати у кожному елементі нашого велитенського масиву на два мільярди значень.
- Це ж скільки на це все пам'яті на це піде.
- До біса, якщо робити все неправильно і не так вже багато, якщо трохи потрюкачити. Наприклад ми можемо використати у якості типу нашого масиву щось таке:
record Entry(int Hash, object Value, Entry? Next);

- Типу зв'язаного списку?
- Так. Ми не будемо витрачати пам'ять, якщо елементу геть немає і не будмо одразу виділяти пам'ять під якусь там колекцію, якщо колізій так і не станеться. А якщо хеш функція добра, то вони не дуже й часті.
- Стоп... А якщо погана?
- Тоді буде цікаво. Це буде працювати хахливо, бо ми витратимо час на обчислювання хешів і нам все одно доведеться порівнювати всі елементи. Але це буде працювати. На цьому побудована одна з можливих атак: якщо знаєш багато колізій для якогось хеша і знаєш де він використовується, то можна спробувати змусити їх використовувати до біса обчислювальної потужності.

Dev Jungles

05 Oct, 10:55


- Почекай. Давай трохи назад. Масив на два мільярди значень звучить не дуже професійно... І якщо один такий на сучасному залізі ми ще можемо собі дозволити, то купу вже навряд.
- О да. Але ми вже навчились боротись з колізіями, тож давай просто скоротемо масив вдвічі, а замість того щоб в якості індексу використовувати сам хеш використаємо залишок від ділення на мільярд.
- А це спрацює. Плюс в нас вже збережений хеш, тож у випадку якщо хеші різні, але залишок однаковий, то ми зможемо не порівнювати самі елементи.
- В точку.
- І так само можна зменьшити масив наскільки завгодно.
- Саме так.
- А через те, що в нас вже є заздалегіть виділиний масив, а, навіть якщо трапляється колізія, то вставка в нього буде дуже швидка.
- Дуже швидка.

І тут я епічно ломаю п'яту стіну не гірше за Френка Андервуда і кажу, що саме так і працює велика політика HashSet у більшості мов програмування. Так само працюють і HashTable і Dictionary, тільки додають до Entry ще саме значення.

На знаю чи комусь корисно, чи всі це пом'ятають з курсів чи універу, але я точно пом'ятаю, що в той час мені було важко це зрозуміти і якесь таке пояснення стало б у нагоді.

Закиньте будь ласка, це комусь, кому це може бути корисним.

#WrittenInTheJungle #HashSeries

Dev Jungles

04 Oct, 16:19


Не відкладай на "потім прочитати": там вже і так багато усього.

Зовсім трошки про хешфункції.

- Що це таке?
- Це такі функції, що:
1. Перетворюють одне значення будь якого розміру на інше значення фіксованого.
2. Для одного і того самого значення завжди повертають одне й те саме значення.
- Тобто це хеш фкнція?
int GetHash(object o) {
return 77;
}
- Так, але паршива
- Що таке гарні хеш функції?
- Це такі функції, що:
1. Що для різних значень частіше за все повертають різні значення
2. Значення в яких розподіленні рівномірно по всьому діапазону можливих значень
- Чому частіше за все?
- Не треба бути дуже розумним, щоб придумати таку хеш функцію для кожного з булєвих значень буде повертати різне ціле число(int).
- Чому?
- Тому що булєвих значень усього 2, а цілих чисел(int) ~4 мільярди.
- Так чому не повертати для всіх об'єктів завжди для різні значення?
- Тому що різних об'єктів може бути більше 4 мільярдів.
- Чому не обрати щось більше за int для хешу? Long чи decimal?
- Бо різних об'єктів ще більше. Що як ми обрали decimal як тип хешу, а нам прийшов об'єкт що може бути decimal, але ще й може бути null, або об'єкт з двох decimal, або decimal і bool?
- Гм.. І що робити?
- Змиритись. Нам не вистачить діапазону можливих значень, тож можуть існувати два різних значення для котрих хеш функція буде давати одні й ті самі результати. Це називають колізією.
- При наявності колізій зі 100% впевненостю ні. Уявимо, що в нас 256 можливих значень хеш функція і 65536 можливих значень. Кожному хешу відповідає 256 різних значень. Хтозна яке з них було орігінальним.
- Щось важко
- Ну давай уявимо, що наша хеш фкнкція просто складає розряди числа: на вхід отримали 12 345, то хешем буде 15. Але такий саме хеш і для 96 і 69 і 159 і купи ще інших чисел.
- Добре. Так і що з цим можна робити?
- Зробити систему, або дуже швидкою, або безпечною, чи навіть все разом, але то наступним разом.


#WrittenInTheJungle #HashSeries

Dev Jungles

18 May, 11:25


А і ще одне.
Жодного відео з каналу ніколи не було видалено: всі доступні в плейлістах.

Dev Jungles

18 May, 11:15


Віддивився перший драфт документалки.
Рома неймовірно попрацював, але я все одно викатив йому чи не пів сотні правок, хоча для 140 хвилин контенту це і непогано. А ще мені до біса подобається який він саундтрек до цього написав. Чорт забирай, в мене буде справжнє кіно з авторським саундтреком!
Все серйозно!

Dev Jungles

16 May, 05:31


Кому як не тим хто боронить Харківщину зараз закидати, вірно?

Це прохання від мого доброго друга і однокласника.

Сам вже долучився.

Dev Jungles

16 May, 05:31


Доброго дня, починаємо збір коштів на РЕБ для 127ї бригади ТрО Харкова, в котрій зараз служить мій вітчим.
РЕБи - занадто важлива річ для захисту особистого складу та автівок від FPV-дронів, збираємо на РЕБ сьогодні, щоб не збирати на нові корчі завтра.

Чепель Михайло
Банка:
https://send.monobank.ua/jar/6yw1jstqjq
Номер картки банки:
5375411218204712

Dev Jungles

30 Apr, 11:29


Як і обіцяв в останньому відео викладаю для своїх любих спонсорів лекцію з далекого 2019 року, "Уроки контролостроения"
А хто ще не спонсор, але хоче подивитись, тож бігом підписуватись на новий рівень!

Dev Jungles

25 Apr, 06:59


Можете вірити, можете ні, але це нове відео на Dev Jungles!
А тема така, що його б треба було випускати на перше квітня, але насправді там все дуже серйозно.
Тож нумо дивитись, коли ще таке буде?

https://youtu.be/3nx_zThOBWo?si=NHWoC_4CAh1TaHcX

Dev Jungles

10 Apr, 10:16


Я злочинно давно не робив нових відео і планую трохи виправитись: тобто в мене вже на руках сценарій нового невеликого відео про тему яка ще не була висвітлена на каналі. Сценарій готовий, це завжди найдовше: залишилось зняти і змонтувати.

Але поки до мене звернувся колега з одного з попередніх місць роботи і попросив допомоги зі збором, що в нього підвис. Сим він служить майже з самого початку повномасштабного вторгнення.

Він у ППО Харкова, тож думаю зрозуміло, що це важливо зараз навіть більше ніж будь коли.
Збирають на шини для кразів і то виявилась, на диво для мене, до біса дорога штука.

Тож долучайтесь.
Я вже закинув на це 5к грн.

🎯Ціль: 173 000.00 ₴

🔗Посилання на банку
https://send.monobank.ua/jar/28VxcUgNjA

💳Номер картки банки
5375 4112 1551 2612

Оригінальний пост Валентина (того самого колеги):
https://www.facebook.com/story.php?id=100001081793764&story_fbid=7393666907345962

Dev Jungles

02 Mar, 12:44


Друзі, вас вітає монтажер і режисер документалки, яка скоро вийде на цьому каналі.
Я знаю як ви всі чекаєте нових відео і вже дуже дуже скоро вони також будуть!
Але зараз я звертаюсь до вас за допомогою!
Ми з Андрієм з Луганська і намагаємось максимально допомагати нашим хлопцям.
Покажіть донатами, як ви хочете нові відоси!

Для військовослужбовців
❗️луганської бойової бригади, які захищають незалежність України на Авдіївському напрямку
👉🏻потрібно 300 000 грн
для закупівлі матеріалів для дообладнання БРДМ противокумулятивним захистом від дронів.

🎯Ціль: 300 000.00 ₴

🔗Посилання на банку
https://send.monobank.ua/jar/Af4ztjkKYq

💳Номер картки банки
5375 4112 1674 8421