Last active
January 15, 2021 23:43
-
-
Save milmazz/f1f7b075300eb01e97a4 to your computer and use it in GitHub Desktop.
The Little Elixir & OTP Guidebook
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule IPv4Parser do | |
@moduledoc """ | |
Exercise 2.8.6 from the book The Little Elixir & OTP Guidebook | |
The idea is to take a look at the IPv4 packet spec and try to write a | |
parser. | |
""" | |
def parse(packet) do | |
<< | |
version :: 4, | |
ihl :: 4, # Internet Header Length | |
_dscp :: 6, # Differentiated Services Code Point | |
_ecn :: 2, # Explicit Congestion Notification | |
_total_length :: binary-size(2), # Includes IP Header and payload (16 bits) | |
_identification :: binary-size(2), # 16 bits | |
_flags :: 3, | |
_fragment_offset :: 13, | |
ttl :: 8, # Time To Live | |
protocol :: 8, | |
_header_checksum :: binary-size(2), # 16 bits | |
src_ip_address :: binary-size(4), # 32 bits | |
dest_ip_address :: binary-size(4), # 32 bits | |
rest :: binary | |
>> = packet | |
if ihl > 5 do | |
# 160 bits is the minimum for the header = 32 x 5 (IHL), options_size = 0 | |
# Otherwise options_size = IHL x 32 - 160 bits | |
options_size = ihl * 32 - 160 | |
<< | |
_options :: size(options_size), | |
_data :: binary | |
>> = rest | |
end | |
# Change the decimal value representation of the Protocol property | |
# to its related keyword | |
protocol = protocol |> protocol_decimal_to_keyword | |
# Transform source and destination IP to a dotted decimal presentation | |
src_ip_address = src_ip_address |> dotted_decimal_ip | |
dest_ip_address = dest_ip_address |> dotted_decimal_ip | |
%{ | |
version: version, | |
protocol: protocol, | |
ttl: ttl, | |
ihl: ihl, | |
src_ip_address: src_ip_address, | |
dest_ip_address: dest_ip_address | |
} | |
end | |
def protocol_decimal_to_keyword(proto) do | |
# The complete list of IP protocol numbers | |
# https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers | |
keywords = [ | |
'1': :ICMP, | |
'2': :IGMP, | |
'6': :TCP | |
] | |
key = proto |> Integer.to_string |> String.to_atom | |
if Keyword.has_key?(keywords, key) do | |
keywords[key] | |
else | |
:error | |
end | |
end | |
def dotted_decimal_ip(ip) do | |
<< | |
first_octet :: size(8), | |
second_octet :: size(8), | |
third_octet :: size(8), | |
fourth_octet :: size(8) | |
>> = ip | |
Enum.map([first_octet, second_octet, third_octet, fourth_octet], fn x -> x |> Integer.to_string end) | |
|> Enum.join(".") | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The Little Elixir & OTP Guidebook | |
# 2.8 Exercises | |
# 6. Take a look at an IPV4 packet. Try writing a parser for that. | |
# TODO: Include more realistic values! | |
# Try to take an example from Wireshark | |
# This is the test for the parser. | |
defmodule Ipv4parserTest do | |
use ExUnit.Case | |
test "IPv4 Header Parser" do | |
packet = << | |
4 :: size(4), # version | |
5 :: size(4), # ihl | |
1 :: size(6), # dscp | |
1 :: size(2), # ecn | |
1 :: size(16), # total_length | |
1 :: size(16), # identification | |
1 :: size(3), # flags | |
1 :: size(13), # fragment_offset | |
1 :: size(8), # ttl | |
6 :: size(8), # protocol | |
1 :: size(16), # header_checksum | |
192 :: size(8), 168 :: size(8), 0 :: size(8), 1 :: size(8), # src_ip_address (32 bits) | |
192 :: size(8), 168 :: size(8), 0 :: size(8), 2 :: size(8), # dest_ip_address (32 bits) | |
1 :: size(8) # rest | |
>> | |
assert %{ | |
version: 4, | |
protocol: :TCP, | |
ttl: 1, | |
ihl: 5, | |
src_ip_address: "192.168.0.1", | |
dest_ip_address: "192.168.0.2" | |
} == IPv4Parser.parse(packet) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The Little Elixir & OTP Guidebook | |
# 2.8 Exercises | |
# 3. Transform [1,[[2],3]] to [9, 4, 1] with and | |
# without the pipe operator. | |
defmodule MyList do | |
def flatten(list) do | |
do_flatten(list, []) | |
end | |
def flat(list) do | |
list | |
|> do_flatten([]) | |
end | |
defp do_flatten([], list) do | |
list | |
end | |
defp do_flatten([h|t], list) when is_list(h) do | |
do_flatten(h ++ t, list) | |
end | |
defp do_flatten([h|t], list) do | |
list = [h * h | list] | |
do_flatten(t, list) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The Little Elixir & OTP Guidebook | |
# 2.8 Exercises | |
# 1. Implement sum/1. This function should take in a | |
# list of numbers, and return the sum of | |
# the list. | |
defmodule MyMath do | |
def sum(list) do | |
do_sum(list, 0) | |
end | |
defp do_sum([], result) do | |
result | |
end | |
defp do_sum([h|t], result) do | |
result = result + h | |
do_sum(t, result) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment