tag:tarasyarema.com,2013:/posts taras 2025-07-17T16:02:29Z Taras tag:tarasyarema.com,2013:Post/2211187 2025-07-16T22:10:05Z 2025-07-17T16:02:29Z How I found my way to contribute to OSS

Disclaimer: I do not consider myself an experienced OSS contributor, but rather someone beginning to understand how OSS actually works.

A (de)motivational story

Since I started programming I've always wanted to contribute to OSS. I do not really know why in particular, but the idea of contributing to something that is used by other people in a non-commercial way seems like a great idea. I tried approaching it in different ways: the hacktoberfest, projects with open issues, implementing random features, etc. None of them worked. I did manage to merge some PRs, but they were not really meaningful for the project nor for me. They were chores. At some point I really thought it was something not meant for me, and that it had a really high barrier to entry.

Nevertheless, a while ago I found a way to actually contribute, and meaningfully! And it's as simple as this: Don't try to contribute, just build something that you feel passionate about! This may seem as a contradiction, but I do believe that a it's a great way (at least for me) to eventually contribute to OSS. Let's break it down.

The algorithm

Picture yourself building something that is really meaningful for you. Chances are that whatever you do will make use of others' work, and that work most of the time is open source. If you use Javascript or Python (or your favourite language to beat the averages) probably some of the libraries you will be using have a Github page with the code. If you are a curious person (which you should be if you are building something) then you for sure have some of the libraries repos open in one of your x > 100 tabs in your browser.

When you are at this point, you are motivated and aiming to have something working as soon as possible, when suddenly you encounter an issue... You spend a few hours debugging (thinking your code is shit) when you finally find the issue: library X does not handle that specific edge case... You then proceed to clone the repo locally, spend another few hours setting it up, debugging, testing and fixing the issue. You point your project to the local (or forked) version and boom, it works! Congrats, you just contributed to OSS 😉

The world is smaller than we think (how many times you found someone in a completely random place that blew your mind? [1]) and almost certainly someone will fall into one of these categories:

  1. Had the issue before
  2. The maintainers of the project are aware (or have an intuition) of the issue
  3. Someone else will find it soon

And I'm not even talking about features here, but rather real issues in the code that appear the same way they do in the company you work where there's an infinite backlog of bugs. But you just fixed one, hence it's meaningful both for you and for the person that will not encounter it after you make your contribution.

Obviously, take this with a grain of salt. You may not directly or as easily contribute to React or numpy, but maybe some of the newer projects that you decide to use yes. Moreover, there's tons of new projects now with all the AI hype. In my case the one I want to talk about is browser-use.

My contribution story

I quit my job a few months ago aiming to start my own company: a software company. I won't go into the details of it yet as we are in stealth still, but we are building a solution to help companies in the nightmare of QA.

For that we use Playwright and (as it's 2025) AI. And browser-use is a great way to have both. It's a Python library that let you interact with a browser using natural language, while maintaining control over the Playwright objects, which are the ones you would use in a good old E2E. As we rely a lot on browser control, and browser-use is in extremely active development, we find bugs from time to time. For some of them we found workaround, but for other we needed to do changes in the library to fix the bug.

For example, at some point there was a missing try ... except in a part of the code, and it was failing in the main branch. We want to use the latest possible version of the library to make use of the new features asap, we found it immediately after a regular upgrade. I did some investigation in the fork I already had, and found that there was some code that when the agent got into a n > 1 step would fail due to a function already being registered in the browser session. It was an easy fix, we just needed to add the check for that specific failure, and continue, as it was not really an operational issue. I did the PR, ran the tests locally, linted, talked with the maintainers and in less than 24h it was merged!

This is just an example, but I'm sure you'll find plenty of other examples in your work, and probably most of the PRs in browser-use come from people outside the org that are in a similar situation. They are building something meaningful for themselves, found that something was broken or missing, and decided to just fix it.

Learnings

On the flip-side, contributing is not always as smooth as the example above. Sometimes you end up in an infinite loop of comments, maintainers take long to reply [2], you end up maintaining a fork as the source of truth due to the dependency you have on that one issue being fixed... But it's all part of the process. Life is not always easy, and that's fine. You just need to find ways to keep pushing, and pushing. And it will come.

I would like to dig a bit deeper on the topic of maintaining a fork as the source of truth. In our case, we use python with uv as the package manager. And as we had the fork we used it as the package download location to ensure any fixes that are pending a merge are used by our code. Here's a snippet of how to do it in the pyproject.toml:

...

dependencies = [
    "browser-use",
	...
]

[tool.uv.sources]
browser-use = { git = "https://github.com/tarasyarema/browser-use", branch = "taras-main" }

...

This snippet will make sure that when you run uv sync the code will be pulled from the fork with the correct branch, commit, etc. Here are the docs from uv around git dependencies.

For example, at the time of writing we have a few PRs open that actually fix some edges cases we found. To use those, we created branch (taras-main) that have both PR branches merged into it.

Final notes

I really hope this post sparked a bit of a hype to build something, which in the end is the seed of how all the projects that we use were created in the first place. Who knows, maybe one day I'll be the one reviewing PRs in a projects that other people use... It's good to day-dream from time to time 🌝

Footnotes

[1] I once found a friend of mine in Thailand during a two week trip, I live in Spain... [2] This is not beef to them, as I assume they have plenty of work so thanks for when they do reply fast!


Sadly, because it's 2025, I want to note that this was written entirely by me and no LLMs were involved in the process.

]]>
Taras Yarema
tag:tarasyarema.com,2013:Post/2211181 2021-03-01T11:00:00Z 2025-07-17T06:54:12Z Why using pointers is important

Introduction

Lately at Capchase we've been working with the Stripe API. The use case can be easily simplified: we need to keep some client data on our side so that we can operate with it easier than fetching it every time. For this purpose we use Go, and with it comes all the marshaling and unmarshaling that we need to handle when working with an API.

Good to us, Stripe offers a handy Go package to work with the API without having to handle all the calls manually. To illustrate this, let's consider that we want to get a specific coupon, for that we could code the following function

import (
    "github.com/stripe/stripe-go/v72"
    "github.com/stripe/stripe-go/v72/coupon"
)

// We consider that the initial API key setup has been done
// stripe.Key = "your_stripe_api_key"

// GetCoupon returns the Stripe coupon for the given id
func GetCoupon(id string) (*stripe.Coupon, error) {
    // Set wanted parameters...
    params := &stripe.CouponParams{}
    return coupon.Get(id, params)
}

Now, whenever we call cp, err := GetCoupon(someID) we will get a Stripe coupon object cp. Now, let's have a look at the definition of this struct:

// Coupon is the resource representing a Stripe coupon.
// For more details see https://stripe.com/docs/api#coupons.
type Coupon struct {
    APIResource

    // Primitive fields
    AmountOff        int64             `json:"amount_off"`
    Created          int64             `json:"created"`
    Deleted          bool              `json:"deleted"`
    DurationInMonths int64             `json:"duration_in_months"`
    ID               string            `json:"id"`
    Livemode         bool              `json:"livemode"`
    MaxRedemptions   int64             `json:"max_redemptions"`
    Name             string            `json:"name"`
    Object           string            `json:"object"`
    PercentOff       float64           `json:"percent_off"`
    RedeemBy         int64             `json:"redeem_by"`
    TimesRedeemed    int64             `json:"times_redeemed"`
    Valid            bool              `json:"valid"`

    // Non-primitive fields
    AppliesTo        *CouponAppliesTo  `json:"applies_to"`
    Currency         Currency          `json:"currency"`
    Duration         CouponDuration    `json:"duration"`
    Metadata         map[string]string `json:"metadata"`
}

Note that I re-ordered the fields so that we have them divided into two groups, the first one with only primitive type fields, that is numerics, strings and booleans, and the following with the rest.

The problem

In particular, when working with Stripe coupons, only one of the fields AmountOff and PercentOff may be set. In fact in the official Stripe API docs we can find this example coupon object

{
  "id": "18OmM8HA",
  "object": "coupon",
  "amount_off": 25,
  "created": 1614605969,
  "currency": "usd",
  "duration": "repeating",
  "duration_in_months": 3,
  "livemode": false,
  "max_redemptions": 10,
  "metadata": {
    "test": "test"
  },
  "name": "25 off",
  "percent_off": null,
  "redeem_by": 1766448000,
  "times_redeemed": 0,
  "valid": true
}

Where percent_off is not set. The funny part comes when you marshal the Go coupon object, which would look something like

{
  "id": "18OmM8HA",
  "object": "coupon",
  "amount_off": 25,
  "created": 1614605969,
  "currency": "usd",
  "duration": "repeating",
  "duration_in_months": 3,
  "livemode": false,
  "max_redemptions": 10,
  "metadata": {
    "test": "test"
  },
  "name": "25 off",
  "percent_off": 0, 
  "redeem_by": 1766448000,
  "times_redeemed": 0,
  "valid": true
}

Why we get a 0 in the percent_off field?

If you have a closer look at the stripe.Coupon struct definition we see that the primitive fields are all non-pointer. Hence, we marshal the struct cp into bytes and look at it we get zero values for all those primitive types which were not set initially (PercentOff, for instance).

Solution

TL;DR: Use pointers 🥴

If we change the primitive fields to be pointers this problem is solved. As, when a field is unmarshaled from a null value, we will get a Go nil of the field type. This may be an issue because it forces the user to handle pointer logic. But one may use pointers always if the returned data can be nullable, because if not false data will eventually be served.

To illustrate, in a more simple example and without using any external package, consider the following code which can be seen in a playground:

package main

import (
    "encoding/json"
    "fmt"
)

type Drama struct {
    Primitive int  `json:"primitive"`
    Ptr       *int `json:"ptr"`
}

func main() {
    // Both fields empty
    d := Drama{}
    b, _ := json.MarshalIndent(d, "", "  ")
    fmt.Println(string(b))
}

The code outputs the following JSON string

{
  "primitive": 0,
  "ptr": null
}

The actual problem of this is that the end user of the Stripe package (or any other package that do not use pointers on nullable fields) it is literally impossible to know whether the field PercentOff was initially empty or not.

Postmortem

When we found the described issue, I immediately created an issue in the package repo, and after getting a better implement your own package response from one of the contributors we decided to fork...

After forking the package repo to our pointered version of stripe-go I spend a number of (not few) hours migrating every primitive field of every struct of stripe to be pointers and making the tests pass... Which end up being much harder than expected because of a hidden forgotten channel in one of the tests (I may talk about that another day).

Finally, we deployed our own version and started using it, which yielded to the wanted results!

If you liked this post ping me on Twitter and... We're hiring! Have a look the open positions at Capchase here.

]]>
Taras Yarema
tag:tarasyarema.com,2013:Post/2211180 2020-03-29T11:00:00Z 2025-07-16T20:49:20Z Advent Of Corona: Day 8

This post is about the day 8 problem of the Advent Of Corona challenge.

The problem

You are given 3 TCP connections:

  • A test one: 68.183.210.250:32779
  • Phase 1: 68.183.210.250:32780
  • Phase 2: 68.183.210.250:32781

Your goal is to get a flag, but... Which flag?

The solution

Disclaimer: I won't give the flags here. Try to solve it by your own, or run the solution script after understanding it if you want to get the flags. It's up to you in the end...

If you play with the test connection, for example running it via telnet:

$ telnet 68.183.210.250 32779

You see a tic-tao-toe game. If you win the bot you will get a flag in the format flag{xxx}.

As the problem states, there's a timeout of 2s and 3s for the first and second phase, respectively.

My approach is to try to solve it via a random algorithm, i.e. just pick a random empty cell every time.

Here is the code:

from telnetlib import Telnet
from time import time

connections = [
    ("test", "68.183.210.250", 32779, 3),
    ("phase_0", "68.183.210.250", 32780, 3),
    ("phase_1", "68.183.210.250", 32781, 6)
]

for t, h, p, s in connections:
    tn = Telnet(h, p)

    tn.read_until(str.encode("give me a name: "))
    tn.write(str.encode("taras"))
    tn.read_until(str.encode("\n"))

    tt = time()

    while True:
        try:
            d = []
            d_tmp = []

            for _ in range(s):
                try:
                    tmp = tn.read_until(str.encode("\n"))

                except Exception:
                    flag = bytes.decode(d_tmp[0]).replace("\n", "")

                    print(f"{t}: {flag} - {time()-tt:2.4f} s.")
                    raise Exception("end")

                d_tmp.append(tmp)
                d += bytes.decode(tmp).replace("\n", "").split("|")[1:-1]
            
            d = [ dd.replace(" ", "") for dd in d ]
            tn.read_until(str.encode("give me a position (comma separated): "))

            for i in range(len(d)):
                if d[i] == "":
                    ss = f"{i//s},{i%s}\n"
                    tn.write(str.encode(ss))
                    break
        
        except Exception:
            break

You may have to run it a few times if it does not solve it in the first run.

The actual code

It was written in Go and deployed via Docker :3

If you are interested in how I created this challenge here you can have a look!

]]>
Taras Yarema
tag:tarasyarema.com,2013:Post/2211178 2020-03-27T11:00:00Z 2025-07-16T20:48:27Z Advent Of Corona: Day 6

This post is about the day 6 problem of the Advent Of Corona challenge.

The problem

You are given a binary named lagrange_baby. That's all.

The solution

The first thing to do when you get a binary in a CTF-like challenge like this is taking a look at what bash commands like file or strings output you.

$ file lagrange_baby
lagrange_baby: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/l, 
for GNU/Linux 3.2.0, BuildID[sha1]=76154f43151d7962511001b781c47a617a01b9bd, 
not stripped
$ strings -d lagrange_baby
/lib64/ld-linux-x86-64.so.2
libc.so.6
srand
__isoc99_scanf
puts
__stack_chk_fail
printf
strlen
malloc
__cxa_finalize
__libc_start_main
free
GLIBC_2.7
GLIBC_2.4
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
gfff
gfff
VUUU
VUUU
AWAVI
AUATL
[]A\A]A^A_
noup
still noup
aaaaaaaaand stil noup
flag{\%s}
;*3$"

From these outputs we know the following things:

  • The binary is Linux x64 executable.
  • There are some interesting strings: noup, still noup, aaaaaaaaand stil noup and flag{\%s}. There is nothing similar to a flag inside it, so I can suppose that it's generated in execution time.

Let's take a deeper look into the binary. I choose Ghidra, but it can be done in any other GUI or terminal tool (r2, cutter, ida, ...).

Playing around

Before doing anything we will try to mess around with the binary:

$ ./lagrange_baby
a
noup
$ ./lagrange_baby
1
noup
$ ./lagrange_baby
aaaaaaaaaaaaaaaaaaa
noup

Hm, interesting...

Reversing with Ghidra

The basic steps to begin analyzing a binary are the following:

Import the binary

Pretty straight-forward.

Analyze it

Just analyze all.

Reversing

First of all, I look that the functions panel and search for the main function. When you click at it it shows up a C-like decompiled view of the function.

One of the things I like about Ghidra is that it lets you redefine variable types and names. So you can change those definitions during the reversing and the flow is much easier to follow.

The first thing I do is redefine the main function to be int main (void) and the __isoc99_scanf to scanf. I notice that there are two scanf in the main function: 00100c17 and 00100d2e. So I redefine the values written to as input_1 and input_2.

We now know the expected types of inputs: uint input_2 and char *input_2.

As we know that the first input is a positive integer, we return and play around a bit:

$ ./lagrange_baby
0
noup
$ ./lagrange_baby
1
noup
$ ./lagrange_baby
2
noup
$ ./lagrange_baby
3
noup
$ ./lagrange_baby
4
noup
$ ./lagrange_baby
5
noup
$ ./lagrange_baby
6
a
still noup

Hm, so 6 is an accepted first input. Good, but why? We will suppose that the function isprime returns what its name says if the number given as input is prime. So if we look at the first part of the main function (I renamed some variables to make it more readable) we have the following flow

scanf(&io_input,&input_1);
srand(input_1); // init rand with seed the input value
var_1 = 0xd;    // 0xd = 13 in decimal
do {
    // this while loops var_1 from 13 untill it breaks
    var_1_prime = isprime(var_1); // Check if the current var_1 is prime
    if ((int)var_1_prime != 0) {
        // if its prime we take its remainder mod 10 and check if its prime
        var_1_prime = isprime((int)var_1 % 10);
        if ((int)var_1_prime != 0) {
            // if it is we take the integer division of var_1 by 10 and
            // check if is divisble by 3
            var_2 = (int)var_1 / 10;
            // then this strange wtf variable comes up...
            if (((int)var_2 % 3 == 0) &&
                (wtf = (uint)((int)var_2 >> 0x1f) >> 0x1f, (int)var_2 % 3 == (var_2 + wtf & 1) - wtf))
                break; // if this strange condition is satisfied we exit the loop
        }
    }
    // var_1++
    var_1 = var_1 + 1;
} while( true );

As I do not really understand this loop, I decided to implement a analogy of this in Python:

def isprime(a):
  return not (a < 2 or any(a % x == 0 for x in range(2, int(a ** 0.5) + 1)))

var_1 = 0xd

while True:
  var_1_prime = isprime(var_1)

  if var_1_prime:
    var_1_prime = isprime(var_1 % 10)

    if var_1_prime:
      var_2 = var_1 // 10
      wtf = (var_2 >> 0x1f) >> 0x1f

      if ((var_2 % 3 == 0) and (var_2 % 3 == (var_2 + wtf & 1) - wtf)):
        break

  var_1 = var_1 + 1

print(var_1, var_2)

When I run it I get this output

$ python3 loop.py
67 6

Nice! We know that var_1=67, and var_2=6. That's where the initial 6 we found came from.

Let's take a look at the next condition

// checks if input_1 is 6
if (var_2 == input_1) {
    // input_2 will be 6 *char
    input_2 = (char *)malloc((long)(int)input_1);
    // read input_2
    scanf(&DAT_00100ea0,input_2);
    input_2_len = strlen(input_2);
    // check if the input_2 is really 6 chars long
    if (input_2_len == (long)(int)input_1) {
        // init a var_3 to 0, which we will loop while
        // its less than 6, i.e. var_3 = 0, 1, 2, 3, 4, 5 
        var_3 = 0;
        while (var_3 < (int)input_1) {
            // pick var_3-th character of the input_2
            char_i = input_2[(long)var_3];
            // evaluate some random function
            epic_function_output = epic_function(var_3 + 1);
            // check if the current character of the input (in decimal form)
            // is the same as the function return value
            if ((int)char_i != (int)epic_function_output) {
                puts("aaaaaaaaand stil noup");
                free(input_2);
                return_var = 1;
                goto LAB_00100def;
            }
            var_3 = var_3 + 1;
        }
        printf("flag{\%s}\n",input_2);
        free(input_2);
        return_var = 0;
    }
    else {
        // the message we got before
        puts("still noup");
        free(input_2);
        return_var = 1;
    }
}

Hm, interesting. Let's try something...

$ ./lagrange_baby
6
123456
aaaaaaaaand stil noup
$ ./lagrange_baby
6
12345
still noup
$ ./lagrange_baby
6
1234567
still noup

Good. The expected input_2 length is really 6 characters long. Let's take a look at the epic_function then. Doing some basic redefines to be more readable we have this

ulong epic_function(int x)
{
  ulong v1;
  ulong v2;
  ulong v3;
  ulong v4;
  
  // 0x3b9aca01 = 1000000001 in decimal
  // but what does pow_mod do?
  v1 = pow_mod(x,5,0x3b9aca01);
  v2 = pow_mod(x,4,0x3b9aca01);
  v3 = pow_mod(x,3,0x3b9aca01);
  v4 = pow_mod(x,2,0x3b9aca01);
  return v4 & 0xffffffff00000000 |
         (ulong)(uint)(int)(((double)x * -7657.00000000) / 6.00000000 +
                            ((double)(int)v4 * 10123.00000000) / 12.00000000 +
                            ((double)(int)v3 * -5861.00000000) / 24.00000000 +
                            ((double)(int)v2 * 389.00000000) / 12.00000000 +
                            ((double)(int)v1 * -13.00000000) / 8.00000000 + 0.00000000 +
                            713.00000000 + 0.50000000);
}

This function seems to do some strange calculations... Let's take a look at pow_mod

ulong pow_mod(int x, uint y, int z)
{
  uint v1;
  int v2;
  uint v3;
  
  v3 = 1;
  v1 = y;
  v2 = x;
  while (v1 != 0) {
    if ((v1 & 1) != 0) {
      v3 = (int)(v2 * v3) % z;
    }
    v1 = (int)v1 >> 1;
    v2 = (v2 * v2) % z;
  }
  return (ulong)v3;
}

Hmm, I'm to lazy to understand this. Let's try to run it! I created a C file with the pow_mod and epic_function definitions and a simple loop like it does on the lagrange_baby binary, from 0 to 5.

#include <stdio.h>

int pow_mod(int x, int y, int z)
{
  int v1;
  int v2;
  int v3;
  
  v3 = 1;
  v1 = y;
  v2 = x;
  while (v1 != 0) {
    if ((v1 & 1) != 0) {
      v3 = (int)(v2 * v3) % z;
    }
    v1 = (int)v1 >> 1;
    v2 = (v2 * v2) % z;
  }
  return (int)v3;
}

int epic_function(int x)
{
  int v1;
  int v2;
  int v3;
  int v4;
  
  // 0x3b9aca01 = 1000000001 in decimal
  // but what does pow_mod do?
  v1 = pow_mod(x,5,0x3b9aca01);
  v2 = pow_mod(x,4,0x3b9aca01);
  v3 = pow_mod(x,3,0x3b9aca01);
  v4 = pow_mod(x,2,0x3b9aca01);
  return v4 & 0xffffffff00000000 |
         (int)(int)(int)(((double)x * -7657.00000000) / 6.00000000 +
                            ((double)(int)v4 * 10123.00000000) / 12.00000000 +
                            ((double)(int)v3 * -5861.00000000) / 24.00000000 +
                            ((double)(int)v2 * 389.00000000) / 12.00000000 +
                            ((double)(int)v1 * -13.00000000) / 8.00000000 + 0.00000000 +
                            713.00000000 + 0.50000000);
}

int main() {
    for (int i = 0; i < 6; ++i)
    {
        printf("%d %d %c\n", i+1, epic_function((int)(i+1)), epic_function((int)(i+1)));
    }
    return 0;
}

Note that I changed all uint and ulong to int. When I compile and execute this I get the following output:

$ gcc epic.c
$ ./a.out
1 67 C
2 48 0
3 114 r
4 111 o
5 78 N
6 52 4

Oh! This look like something. We got the string: C0r0N4! Let's try it as second input:

$ ./lagrange_baby
6
C0roN4
flag{C0roN4}

Yes! We got it. Not that hard, right?

Actual C code for the binary

If any of you are interested in how I created this challenge, here is the C code that generated it:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define TOO_BIG 1e+9+1

int pow_mod(int a, int x, int n)
{
   int r = 1;

   while (x)
   {
      if ((x & 1) == 1)
         r = a * r % n;

      x >>= 1;
      a = a * a % n;
   }

   return r;
}

int epic_function(int x)
{
   double s = 0;

   s += -13 * (double)pow_mod(x, 5, TOO_BIG) / 8;
   s += 389 * (double)pow_mod(x, 4, TOO_BIG) / 12;
   s += -5861 * (double)pow_mod(x, 3, TOO_BIG) / 24;
   s += 10123 * (double)pow_mod(x, 2, TOO_BIG) / 12;
   s += -7657 * (double)x / 6;
   s += 713;

   return (int)(s + 0.5);
}

int rand_between(int a, int b) { return a + (int)((double)(b - a + 1) * rand() / (TOO_BIG + 1.0)); }

int isprime(int n)
{
   int k = 5;

   if (n == 2 || n == 3)
      return 1;
   if (n <= 1 || !(n & 1))
      return 0;

   int s = 0;
   for (int m = n - 1; !(m & 1); ++s, m >>= 1)
      ;

   int d = (n - 1) / (1 << s);

   for (int i = 0; i < k; ++i)
   {
      int a = rand_between(2, n - 2);
      int x = pow_mod(a, d, n);

      if (x == 1 || x == n - 1)
         continue;

      for (int r = 1; r <= s - 1; ++r)
      {
         x = pow_mod(x, 2, n);
         if (x == 1)
            return 0;
         if (x == n - 1)
            goto LOOP;
      }

      return 0;
   LOOP:
      continue;
   }

   return 1;
}

int main()
{
   int y;

   scanf("\%d", &y);
   srand(y);

   for (int i = 13;; i++)
   {
      if (isprime(i) && isprime(i % 10))
      {
         int tmp = i / 10;

         if (!(tmp % 3) && (tmp % 3 == tmp % 2))
         {
            if (tmp == y)
               break;
            else
            {
               printf("noup\n");
               return 1;
            }
         }
      }
   }

   char *x = (char *)malloc(y * sizeof(char));
   scanf("\%s", x);

   if (strlen(x) != y)
   {
      printf("still noup\n");
      free(x);
      return 1;
   }

   for (int i = 0; i < y; i++)
   {
      if ((int)x[i] != epic_function(i + 1))
      {
         printf("aaaaaaaaand stil noup\n");
         free(x);
         return 1;
      }
   }

   printf("flag{\%s}\n", x);
   free(x);

   return 0;
}
]]>
Taras Yarema