wliment
4/28/2015 - 11:18 PM

## The Little Elixir & OTP Guidebook

The Little Elixir & OTP Guidebook

``````# 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``````
``````# 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``````
``````# 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

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
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,
} == IPv4Parser.parse(packet)
end
end``````
``````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
_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

%{
version: version,
protocol: protocol,
ttl: ttl,
ihl: ihl,
}
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
``````