Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
The Little Elixir & OTP Guidebook
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
# 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
# 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
# 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