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

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