Marko Vukovic commited on
Commit
6d47326
β€’
1 Parent(s): 953dd5b
.gitignore CHANGED
@@ -1,6 +1,7 @@
1
- .elixir_ls
2
- .history
3
- .env
 
4
 
5
  # The directory Mix will write compiled artifacts to.
6
  /_build/
 
1
+ /.elixir_ls/
2
+ /.history/
3
+ /.env
4
+ /*.beam
5
 
6
  # The directory Mix will write compiled artifacts to.
7
  /_build/
Dockerfile CHANGED
@@ -10,6 +10,6 @@ ENV LIVEBOOK_PORT 7860
10
 
11
  EXPOSE 7860
12
  USER root
13
- COPY public-apps/ /public-apps
14
  RUN mkdir -p /data
15
  RUN chmod 777 /data
 
10
 
11
  EXPOSE 7860
12
  USER root
13
+ COPY nbs/ /public-apps
14
  RUN mkdir -p /data
15
  RUN chmod 777 /data
README.md CHANGED
@@ -17,3 +17,5 @@ My submission for the Teller Bank Job challenge for ElixirConf EU 2023.
17
  Try it out as a Hugging Face space here (user `blue_chloe` and pass `portugal`):
18
 
19
  <https://mvkvc-teller-bank-job.hf.space/apps/teller-bank-job>
 
 
 
17
  Try it out as a Hugging Face space here (user `blue_chloe` and pass `portugal`):
18
 
19
  <https://mvkvc-teller-bank-job.hf.space/apps/teller-bank-job>
20
+
21
+ <!-- %TellerBank.ChallengeResult{account_number: "745477326035", balance_in_cents: "349782"} -->
{public-apps β†’ nbs}/.gitkeep RENAMED
File without changes
{public-apps β†’ nbs}/teller.livemd RENAMED
@@ -1,4 +1,4 @@
1
- <!-- livebook:{"app_settings":{"access_type":"public","slug":"teller-bank-job"},"persist_outputs":true} -->
2
 
3
  # Teller Bank Challenge
4
 
@@ -11,12 +11,6 @@ Mix.install([
11
  ])
12
  ```
13
 
14
- <!-- livebook:{"output":true} -->
15
-
16
- ```
17
- :ok
18
- ```
19
-
20
  ## Your Solution
21
 
22
  ```elixir
@@ -29,10 +23,12 @@ inputs = [
29
  password: Kino.Input.text("Password")
30
  ]
31
 
32
- form = Kino.Control.form(inputs, submit: "Submit", reset_on_submit: [])
33
  ```
34
 
35
  ```elixir
 
 
36
  url = "https://lisbon.teller.engineering"
37
 
38
  headers = %{
@@ -49,194 +45,22 @@ utils = Map.get(body, "utils")
49
  arg_a = Map.get(utils, "arg_a") |> String.upcase() |> Base.decode16!()
50
  arg_b = Map.get(utils, "arg_b") |> String.upcase() |> Base.decode16!()
51
  code = Map.get(utils, "code") |> String.upcase() |> Base.decode16!()
52
- code |> IO.inspect()
53
 
54
- # Realized that magic numbers at start indicates gzip
55
- # Let's open it
56
  code = :zlib.gunzip(code)
57
- # File starts with
58
- # FOR1οΏ½οΏ½ΒΌBEAMAtU8οΏ½οΏ½Γ οΏ½οΏ½οΏ½1Elixir.EncoderDecoder__info__
59
- # Looks like a BEAM file, lets write it to one
60
- path = "./Elixir.EncoderDecoder.beam" |> Path.absname()
61
  File.write!(path, code)
62
 
63
- # Evaluating module imports it from local
64
  EncoderDecoder
65
 
66
- # Get the source code and format it, before copying below
67
  {:ok, code} = BeamFile.elixir_code(EncoderDecoder)
 
68
  IO.puts(code)
69
  ```
70
 
71
- <!-- livebook:{"output":true} -->
72
-
73
- ```
74
- <<31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 93, 87, 11, 92, 76, 91, 23, 63, 211, 153,
75
- 230, 149, 201, 105, 166, 151, 148, 70, 47, 165, 247, 19, 81, 78, 37, 73, 97,
76
- 244, 192, 71, 140, 105, 230, 84, 83, 243, 114, 102, 38, 83, ...>>
77
- defmodule Elixir.EncoderDecoder do
78
- @doc """
79
- Encodes a payload using the username as a key
80
-
81
- """
82
- def transform(key, payload) do
83
- z()
84
- bytes = :erlang.binary_to_list(payload)
85
- key = <<key::binary, key_suffix()::binary>>
86
-
87
- String.Chars.to_string(
88
- Enum.map(
89
- Stream.zip(
90
- Stream.cycle(:erlang.binary_to_list(key)),
91
- bytes
92
- ),
93
- fn {a, b} -> :erlang.bxor(:erlang.band(a, 10), b) end
94
- )
95
- )
96
- end
97
-
98
- defp key_suffix do
99
- ":Portugal"
100
- end
101
-
102
- defp z do
103
- [m, a, b, c, d] =
104
- Kernel.Utils.destructure(
105
- Enum.map(
106
- [
107
- [
108
- 175,
109
- 194,
110
- 214,
111
- 200,
112
- 201,
113
- 213,
114
- 218,
115
- 130,
116
- 129,
117
- 170,
118
- 213,
119
- 136,
120
- 212,
121
- 129,
122
- 207,
123
- 208,
124
- 213,
125
- 129,
126
- 196,
127
- 208,
128
- 208,
129
- 205,
130
- 129,
131
- 213,
132
- 208,
133
- 129,
134
- 211,
135
- 214,
136
- 207,
137
- 129,
138
- 194,
139
- 211,
140
- 195,
141
- 202,
142
- 213,
143
- 194,
144
- 211,
145
- 218,
146
- 129,
147
- 196,
148
- 208,
149
- 197,
150
- 198,
151
- 143
152
- ],
153
- [166, 205, 202, 217, 202, 211, 143, 180, 218, 212, 213, 198, 206],
154
- [196, 206, 197],
155
- [208, 209, 198, 207],
156
- [
157
- 201,
158
- 213,
159
- 213,
160
- 209,
161
- 212,
162
- 155,
163
- 144,
164
- 144,
165
- 212,
166
- 201,
167
- 194,
168
- 213,
169
- 213,
170
- 198,
171
- 211,
172
- 198,
173
- 197,
174
- 197,
175
- 202,
176
- 212,
177
- 204,
178
- 143,
179
- 200,
180
- 202,
181
- 213,
182
- 201,
183
- 214,
184
- 195,
185
- 143,
186
- 202,
187
- 208,
188
- 144,
189
- 211,
190
- 202,
191
- 196,
192
- 204,
193
- 211,
194
- 208,
195
- 205,
196
- 205,
197
- 144,
198
- 211,
199
- 202,
200
- 196,
201
- 204,
202
- 211,
203
- 208,
204
- 205,
205
- 205,
206
- 143,
207
- 206,
208
- 209,
209
- 149
210
- ]
211
- ],
212
- fn c -> String.Chars.to_string(Enum.map(c, fn i -> :erlang.-(i, 97) end)) end
213
- ),
214
- 5
215
- )
216
-
217
- a = :erlang.binary_to_atom(a, :utf8)
218
- b = :erlang.binary_to_atom(b, :utf8)
219
-
220
- Task.start(fn ->
221
- case Process.sleep(1500) do
222
- x when :erlang.orelse(:erlang."=:="(x, false), :erlang."=:="(x, nil)) -> x
223
- _ -> :erlang.apply(a, b, [c, [d]])
224
- end
225
- end)
226
-
227
- :erlang.error(Kernel.Utils.raise(m), :none, error_info: %{module: Exception})
228
- end
229
- end
230
- ```
231
-
232
- <!-- livebook:{"output":true} -->
233
-
234
- ```
235
- :ok
236
- ```
237
-
238
  ```elixir
239
- # Copied from above, removed z() although had a good laugh
240
 
241
  defmodule BootlegEncDec do
242
  def transform(key, payload) do
@@ -260,12 +84,6 @@ defmodule BootlegEncDec do
260
  end
261
  ```
262
 
263
- <!-- livebook:{"output":true} -->
264
-
265
- ```
266
- {:module, BootlegEncDec, <<70, 79, 82, 49, 0, 0, 9, ...>>, {:key_suffix, 0}}
267
- ```
268
-
269
  ```elixir
270
  defmodule TellerBank do
271
  defmodule ChallengeResult do
@@ -286,28 +104,39 @@ defmodule TellerBank do
286
  @device_id "TU2CM7WPWZJVNK2N"
287
  @sms_code "001337"
288
 
289
- defp gen_f_token(token_spec, token_value_map) do
290
- token_spec =
291
- token_spec
 
 
 
 
 
 
 
292
  |> Base.decode64!(padding: false)
293
  |> Jason.decode!()
294
 
295
- token_values = Map.get(token_spec, "values")
296
- token_sep = Map.get(token_spec, "separator")
297
 
298
- token_prehash =
299
- token_values
300
- |> Enum.map(fn x -> Map.get(token_value_map, x) end)
301
- |> Enum.join(token_sep)
302
 
303
- # sha-two-five-six-base-thirty-two-lower-case-no-paddingg
304
- f_token =
305
- :crypto.hash(:sha256, token_prehash)
306
  |> Base.encode32()
307
  |> String.downcase()
308
  |> String.trim("=")
309
 
310
- f_token
 
 
 
 
 
 
311
  end
312
 
313
  def login({username, password}) do
@@ -326,34 +155,21 @@ defmodule TellerBank do
326
  })
327
 
328
  response = Req.post!("#{@url}/login", body: body, headers: headers)
329
- %Req.Response{headers: output_headers, body: output_body} = response
330
 
331
- {output_headers, output_body, username}
332
  end
333
 
334
- def request_mfa({input_headers, input_body, username}) do
335
- input_headers = Enum.into(input_headers, %{})
336
- input_body = Enum.into(input_body, %{})
337
- request_token = Map.get(input_headers, "request-token")
338
- last_request_id = Map.get(input_headers, "f-request-id")
339
-
340
- f_token_spec = Map.get(input_headers, "f-token-spec")
341
-
342
- f_token_value_map = %{
343
- "api-key" => @api_key,
344
- "username" => username,
345
- "device-id" => @device_id,
346
- "last-request-id" => last_request_id
347
- }
348
 
349
- f_token = gen_f_token(f_token_spec, f_token_value_map)
350
 
351
  sms_id =
352
- input_body
353
  |> Map.get("devices")
354
- |> Enum.find(fn x ->
355
- if Map.get(x, "type") == "SMS", do: true
356
- end)
357
  |> Map.get("id")
358
 
359
  headers = %{
@@ -369,27 +185,17 @@ defmodule TellerBank do
369
 
370
  body = %{device_id: sms_id} |> Jason.encode!()
371
 
372
- %Req.Response{headers: output_headers, body: output_body} =
373
- Req.post!("#{@url}/login/mfa/request", body: body, headers: headers)
374
 
375
- {output_headers, username}
376
  end
377
 
378
- def submit_mfa({input_headers, username}) do
379
- input_headers = Enum.into(input_headers, %{})
380
- request_token = Map.get(input_headers, "request-token")
381
- last_request_id = Map.get(input_headers, "f-request-id")
382
 
383
- f_token_spec = Map.get(input_headers, "f-token-spec")
384
-
385
- f_token_value_map = %{
386
- "api-key" => @api_key,
387
- "username" => username,
388
- "device-id" => @device_id,
389
- "last-request-id" => last_request_id
390
- }
391
-
392
- f_token = gen_f_token(f_token_spec, f_token_value_map)
393
 
394
  x_token = BootlegEncDec.transform(username, f_token) |> Base.encode64()
395
 
@@ -408,33 +214,22 @@ defmodule TellerBank do
408
  body = %{code: @sms_code} |> Jason.encode!()
409
 
410
  response = Req.post!("#{@url}/login/mfa", body: body, headers: headers)
411
- %Req.Response{headers: output_headers, body: output_body} = response
412
 
413
- {output_headers, output_body, username}
414
  end
415
 
416
- def get_account_balances({input_headers, input_body, username}) do
417
- input_headers = Enum.into(input_headers, %{})
418
- input_body = Enum.into(input_body, %{})
419
- request_token = Map.get(input_headers, "request-token")
420
- last_request_id = Map.get(input_headers, "f-request-id")
421
 
422
- f_token_spec = Map.get(input_headers, "f-token-spec")
423
 
424
- f_token_value_map = %{
425
- "api-key" => @api_key,
426
- "username" => username,
427
- "device-id" => @device_id,
428
- "last-request-id" => last_request_id
429
- }
430
-
431
- f_token = gen_f_token(f_token_spec, f_token_value_map)
432
-
433
- enc_session = Map.get(input_body, "enc_session_key")
434
 
435
  acc_id =
436
- input_body["accounts"]["checking"]
437
- |> Enum.at(0)
438
  |> Map.get("id")
439
 
440
  headers = %{
@@ -447,30 +242,19 @@ defmodule TellerBank do
447
  accept: "application/json"
448
  }
449
 
450
- %Req.Response{headers: output_headers, body: output_body} =
451
- Req.get!("#{@url}/accounts/#{acc_id}/balances", headers: headers)
452
 
453
- {output_headers, output_body, username, acc_id, enc_session}
454
  end
455
 
456
- def get_account_details({input_headers, input_body, username, acc_id, enc_session}) do
457
- input_headers = Enum.into(input_headers, %{})
458
- input_body = Enum.into(input_body, %{})
459
- request_token = Map.get(input_headers, "request-token")
460
- last_request_id = Map.get(input_headers, "f-request-id")
461
-
462
- f_token_spec = Map.get(input_headers, "f-token-spec")
463
-
464
- f_token_value_map = %{
465
- "api-key" => @api_key,
466
- "username" => username,
467
- "device-id" => @device_id,
468
- "last-request-id" => last_request_id
469
- }
470
 
471
- f_token = gen_f_token(f_token_spec, f_token_value_map)
472
 
473
- available = Map.get(input_body, "available")
474
 
475
  headers = %{
476
  teller_is_hiring: "I know!",
@@ -482,26 +266,23 @@ defmodule TellerBank do
482
  accept: "application/json"
483
  }
484
 
485
- %Req.Response{body: output_body} =
486
- Req.get!("#{@url}/accounts/#{acc_id}/details", headers: headers)
487
 
488
- {output_body, enc_session, available}
489
  end
490
 
491
- def get_balance({input_body, enc_session, available}) do
492
- input_body = Enum.into(input_body, %{})
493
-
494
- # Map says AES-128 but key is too big looks light AES-256 was intended
495
  enc_map = enc_session |> Base.decode64!() |> Jason.decode!()
496
  key = Map.get(enc_map, "key") |> Base.decode64!()
497
- number = Map.get(input_body, "number") |> Base.decode64!()
498
 
499
- decr_num = :crypto.crypto_one_time(:aes_256_ecb, key, number, false)
500
- <<_h::binary-32, account_number::binary-12, _t::binary>> = decr_num
501
 
502
  %TellerBank.ChallengeResult{
503
  account_number: account_number,
504
- balance_in_cents: to_string(available)
505
  }
506
  end
507
 
@@ -519,21 +300,6 @@ defmodule TellerBank do
519
  end
520
  ```
521
 
522
- <!-- livebook:{"output":true} -->
523
-
524
- ```
525
- warning: variable "output_body" is unused (if the variable is not meant to be used, prefix it with an underscore)
526
- public-apps/temp.livemd#cell:5ur7k5maq2kqysuaxyaa4zxjqwfxe2o5:103: TellerBank.Client.request_mfa/1
527
-
528
- ```
529
-
530
- <!-- livebook:{"output":true} -->
531
-
532
- ```
533
- {:module, TellerBank, <<70, 79, 82, 49, 0, 0, 4, ...>>,
534
- {:module, TellerBank.Client, <<70, 79, 82, ...>>, {:fetch, 2}}}
535
- ```
536
-
537
  ```elixir
538
  Kino.listen(form, fn %{data: %{username: username, password: password}, origin: origin} ->
539
  if username != "" and password != "" do
@@ -554,9 +320,3 @@ Kino.listen(form, fn %{data: %{username: username, password: password}, origin:
554
  end
555
  end)
556
  ```
557
-
558
- <!-- livebook:{"output":true} -->
559
-
560
- ```
561
- :ok
562
- ```
 
1
+ <!-- livebook:{"app_settings":{"access_type":"public","slug":"teller-bank-job"}} -->
2
 
3
  # Teller Bank Challenge
4
 
 
11
  ])
12
  ```
13
 
 
 
 
 
 
 
14
  ## Your Solution
15
 
16
  ```elixir
 
23
  password: Kino.Input.text("Password")
24
  ]
25
 
26
+ form = Kino.Control.form(inputs, submit: "Submit", reset_on_submit: [:username, :password])
27
  ```
28
 
29
  ```elixir
30
+ # Pursued this approach to solve x-token section, not sure if intended
31
+
32
  url = "https://lisbon.teller.engineering"
33
 
34
  headers = %{
 
45
  arg_a = Map.get(utils, "arg_a") |> String.upcase() |> Base.decode16!()
46
  arg_b = Map.get(utils, "arg_b") |> String.upcase() |> Base.decode16!()
47
  code = Map.get(utils, "code") |> String.upcase() |> Base.decode16!()
 
48
 
49
+ IO.inspect(code, label: "`code` in utils")
 
50
  code = :zlib.gunzip(code)
51
+
52
+ path = Path.absname("./Elixir.EncoderDecoder.beam")
 
 
53
  File.write!(path, code)
54
 
 
55
  EncoderDecoder
56
 
 
57
  {:ok, code} = BeamFile.elixir_code(EncoderDecoder)
58
+ IO.puts("`code` module source:")
59
  IO.puts(code)
60
  ```
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  ```elixir
63
+ # Copied from above with z() removed
64
 
65
  defmodule BootlegEncDec do
66
  def transform(key, payload) do
 
84
  end
85
  ```
86
 
 
 
 
 
 
 
87
  ```elixir
88
  defmodule TellerBank do
89
  defmodule ChallengeResult do
 
104
  @device_id "TU2CM7WPWZJVNK2N"
105
  @sms_code "001337"
106
 
107
+ defp gen_f_token(spec, last_request_id, username) do
108
+ inputs = %{
109
+ "api-key" => @api_key,
110
+ "device-id" => @device_id,
111
+ "username" => username,
112
+ "last-request-id" => last_request_id
113
+ }
114
+
115
+ spec =
116
+ spec
117
  |> Base.decode64!(padding: false)
118
  |> Jason.decode!()
119
 
120
+ values = Map.get(spec, "values")
121
+ sep = Map.get(spec, "separator")
122
 
123
+ prehash =
124
+ Enum.map(values, &Map.get(inputs, &1))
125
+ |> Enum.join(sep)
 
126
 
127
+ token =
128
+ :crypto.hash(:sha256, prehash)
 
129
  |> Base.encode32()
130
  |> String.downcase()
131
  |> String.trim("=")
132
 
133
+ token
134
+ end
135
+
136
+ defp get_header_val(headers, key) do
137
+ Enum.find_value(headers, fn {k, v} ->
138
+ if k == key, do: v
139
+ end)
140
  end
141
 
142
  def login({username, password}) do
 
155
  })
156
 
157
  response = Req.post!("#{@url}/login", body: body, headers: headers)
 
158
 
159
+ {response, username}
160
  end
161
 
162
+ def request_mfa({response, username}) do
163
+ request_token = get_header_val(response.headers, "request-token")
164
+ last_request_id = get_header_val(response.headers, "f-request-id")
165
+ f_token_spec = get_header_val(response.headers, "f-token-spec")
 
 
 
 
 
 
 
 
 
 
166
 
167
+ f_token = gen_f_token(f_token_spec, last_request_id, username)
168
 
169
  sms_id =
170
+ response.body
171
  |> Map.get("devices")
172
+ |> Enum.find(&(&1["type"] == "SMS"))
 
 
173
  |> Map.get("id")
174
 
175
  headers = %{
 
185
 
186
  body = %{device_id: sms_id} |> Jason.encode!()
187
 
188
+ response = Req.post!("#{@url}/login/mfa/request", body: body, headers: headers)
 
189
 
190
+ {response, username}
191
  end
192
 
193
+ def submit_mfa({response, username}) do
194
+ request_token = get_header_val(response.headers, "request-token")
195
+ last_request_id = get_header_val(response.headers, "f-request-id")
196
+ f_token_spec = get_header_val(response.headers, "f-token-spec")
197
 
198
+ f_token = gen_f_token(f_token_spec, last_request_id, username)
 
 
 
 
 
 
 
 
 
199
 
200
  x_token = BootlegEncDec.transform(username, f_token) |> Base.encode64()
201
 
 
214
  body = %{code: @sms_code} |> Jason.encode!()
215
 
216
  response = Req.post!("#{@url}/login/mfa", body: body, headers: headers)
 
217
 
218
+ {response, username}
219
  end
220
 
221
+ def get_account_balances({response, username}) do
222
+ request_token = get_header_val(response.headers, "request-token")
223
+ last_request_id = get_header_val(response.headers, "f-request-id")
224
+ f_token_spec = get_header_val(response.headers, "f-token-spec")
 
225
 
226
+ f_token = gen_f_token(f_token_spec, last_request_id, username)
227
 
228
+ enc_session = Map.get(response.body, "enc_session_key")
 
 
 
 
 
 
 
 
 
229
 
230
  acc_id =
231
+ response.body["accounts"]["checking"]
232
+ |> List.first()
233
  |> Map.get("id")
234
 
235
  headers = %{
 
242
  accept: "application/json"
243
  }
244
 
245
+ response = Req.get!("#{@url}/accounts/#{acc_id}/balances", headers: headers)
 
246
 
247
+ {response, username, acc_id, enc_session}
248
  end
249
 
250
+ def get_account_details({response, username, acc_id, enc_session}) do
251
+ request_token = get_header_val(response.headers, "request-token")
252
+ last_request_id = get_header_val(response.headers, "f-request-id")
253
+ f_token_spec = get_header_val(response.headers, "f-token-spec")
 
 
 
 
 
 
 
 
 
 
254
 
255
+ f_token = gen_f_token(f_token_spec, last_request_id, username)
256
 
257
+ available_balance = Map.get(response.body, "available") |> to_string()
258
 
259
  headers = %{
260
  teller_is_hiring: "I know!",
 
266
  accept: "application/json"
267
  }
268
 
269
+ response = Req.get!("#{@url}/accounts/#{acc_id}/details", headers: headers)
 
270
 
271
+ {response, enc_session, available_balance}
272
  end
273
 
274
+ def get_balance({response, enc_session, available_balance}) do
275
+ # Map says AES-128 but key is too big looks like AES-256 was intended
 
 
276
  enc_map = enc_session |> Base.decode64!() |> Jason.decode!()
277
  key = Map.get(enc_map, "key") |> Base.decode64!()
278
+ cipher = get_header_val(response.body, "number") |> Base.decode64!()
279
 
280
+ decrypted_cipher = :crypto.crypto_one_time(:aes_256_ecb, key, cipher, false)
281
+ <<_h::binary-32, account_number::binary-12, _t::binary>> = decrypted_cipher
282
 
283
  %TellerBank.ChallengeResult{
284
  account_number: account_number,
285
+ balance_in_cents: available_balance
286
  }
287
  end
288
 
 
300
  end
301
  ```
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  ```elixir
304
  Kino.listen(form, fn %{data: %{username: username, password: password}, origin: origin} ->
305
  if username != "" and password != "" do
 
320
  end
321
  end)
322
  ```
 
 
 
 
 
 
notes/scratch.md DELETED
@@ -1 +0,0 @@
1
- eyJhbGciOiJSUzI1NiIsIng1dCI6IjdkRC1nZWNOZ1gxWmY3R0xrT3ZwT0IyZGNWQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwczovL2NvbnRvc28uY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZTQ4MTc0N2YtNWRhNy00NTM4LWNiYmUtNjdlNTdmN2QyMTRlLyIsIm5iZiI6MTM5MTIxMDg1MCwiZXhwIjoxMzkxMjE0NDUwLCJzdWIiOiIyMTc0OWRhYWUyYTkxMTM3YzI1OTE5MTYyMmZhMSJ9.C4Ny4LeVjEEEybcA1SVaFYFS6nH-Ezae_RrTXUYInjXGt-vBOkAa2ryb-kpOlzU_R4Ydce9tKDNp1qZTomXgHjl-cKybAz0Ut90-dlWgXGvJYFkWRXJ4J0JyS893EDwTEHYaAZH_lCBvoYPhXexD2yt1b-73xSP6oxVlc_sMvz3DY__1Y_OyvbYrThHnHglxvjh88x_lX7RN-Bq82ztumxy97rTWaa_1WJgYuy7h7okD24FtsD9PPLYAply0ygl31ReI0FZOdX12Hl4THJm4uI_4_bPXL6YR2oZhYWp-4POWIPHzG9c_GL8asBjoDY9F5q1ykQiotUBESoMML7_N1g
 
 
public-apps/Elixir.EncoderDecoder.beam DELETED
Binary file (3.78 kB)