diff --git a/Makefile.dep b/Makefile.dep
index db312db47c297fdbe02be66984cb3f6fbd2b507f..02c957b9c9700f255825d4dfbe8e7fc9c469628d 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -43,6 +43,11 @@ ifneq (,$(filter gnrc_zep,$(USEMODULE)))
   USEMODULE += vtimer
 endif
 
+ifneq (,$(filter gnrc_tftp,$(USEMODULE)))
+  USEMODULE += gnrc_udp
+  USEMODULE += xtimer
+endif
+
 ifneq (,$(filter gnrc_rpl,$(USEMODULE)))
   USEMODULE += fib
   USEMODULE += gnrc_ipv6_router_default
diff --git a/examples/gnrc_tftp/Makefile b/examples/gnrc_tftp/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b92f8e951c83b361e7959bfe8c6aa841cc9b574c
--- /dev/null
+++ b/examples/gnrc_tftp/Makefile
@@ -0,0 +1,42 @@
+# name of your application
+APPLICATION = gnrc_tftp
+
+# If no BOARD is found in the environment, use this default:
+BOARD ?= native
+
+# This has to be the absolute path to the RIOT base directory:
+RIOTBASE ?= $(CURDIR)/../..
+
+BOARD_INSUFFICIENT_MEMORY := airfy-beacon chronos msb-430 msb-430h nrf51dongle \
+                          nrf6310 nucleo-f334 pca10000 pca10005 spark-core \
+                          stm32f0discovery telosb weio wsn430-v1_3b wsn430-v1_4 \
+                          yunjia-nrf51822 z1
+
+# Include packages that pull up and auto-init the link layer.
+# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present
+USEMODULE += gnrc_netif_default
+USEMODULE += auto_init_gnrc_netif
+# Specify the mandatory networking modules for IPv6 and UDP
+USEMODULE += gnrc_ipv6_router_default
+USEMODULE += gnrc_udp
+# Add a routing protocol
+USEMODULE += gnrc_rpl
+# This application dumps received packets to STDIO using the pktdump module
+USEMODULE += gnrc_pktdump
+# Additional networking modules that can be dropped if not needed
+USEMODULE += gnrc_icmpv6_echo
+USEMODULE += gnrc_tftp
+# Add also the shell, some shell commands
+USEMODULE += shell
+USEMODULE += shell_commands
+USEMODULE += ps
+
+# Comment this out to disable code in RIOT that does safety checking
+# which is not needed in a production environment but helps in the
+# development process:
+CFLAGS += -DDEVELHELP
+
+# Change this to 0 show compiler invocation lines by default:
+QUIET ?= 1
+
+include $(RIOTBASE)/Makefile.include
diff --git a/examples/gnrc_tftp/README.md b/examples/gnrc_tftp/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..2627976a2e8cb951d99c588bfd35a253f497200b
--- /dev/null
+++ b/examples/gnrc_tftp/README.md
@@ -0,0 +1,86 @@
+# gnrc_tftp example
+
+## Connecting RIOT native and the Linux host
+
+> **Note:** RIOT does not support IPv4, so you need to stick to IPv6 anytime. To establish a connection between RIOT and the Linux host,
+you will need `tftp` (with IPv6 support). On Ubuntu and Debian you would need the package `tftp-hpa` for TFTP client and `tftpd-hpa` for TFTP server.
+Be aware that many programs require you to add an option such as -6 to tell them to use IPv6, otherwise they
+will fail. If you're using a _Raspberry Pi_, run `sudo modprobe ipv6` before trying this example, because raspbian does not load the
+IPv6 module automatically.
+On some systems (openSUSE for example), the _firewall_ may interfere, and prevent some packets to arrive at the application (they will
+however show up in Wireshark, which can be confusing). So be sure to adjust your firewall rules, or turn it off (who needs security anyway).
+
+First, create a tap interface (to which RIOT will connect) and a bridge (to which Linux will connect) from the RIOT main directory run:
+
+    ./dist/tools/tapsetup/tapsetup -c 1
+
+Now you can start the `gnrc_tftp` example by invoking `make term`. This should automatically connect to the `tap0` interface. If
+this doesn't work for some reason, run `make` without any arguments, and then run the binary manually like so (assuming you are in the `examples/gnrc_tftp` directory):
+
+To verify that there is connectivity between RIOT and Linux, go to the RIOT console and run `ifconfig`:
+
+    > ifconfig
+	Iface  6   HWaddr: 7e:ed:d2:ee:e1:07
+           MTU:1280
+           Source address length: 6
+           Link type: wired
+           inet6 addr: ff02::1/128  scope: local [multicast]
+           inet6 addr: fe80::7ced:d2ff:feee:e107/64  scope: local
+           inet6 addr: ff02::1:ffee:e107/128  scope: local [multicast]
+
+
+Copy the [link-local address](https://en.wikipedia.org/wiki/Link-local_address) of the RIOT node (prefixed with `fe80`) and try to ping it **from the Linux node**:
+
+    ping6 fe80::7ced:d2ff:feee:e107%tapbr0
+
+Note that the interface on which to send the ping needs to be appended to the IPv6 address, `%tapbr0` in the above example. When talking to the RIOT node, you always want to send to/receive from the `tapbr0` interface.
+
+If the pings succeed you can go on to send UDP packets. To do that, first start a UDP server on the RIOT node:
+
+    > tftps start
+    tftp_server: Starting TFTP service at port 69
+
+Now, on the Linux host, you can run tftp to connect with RIOT's TFTP server:
+
+    $ tftp -v -6 fe80::7ced:d2ff:feee:e107%tapbr0 -c get welcome.txt
+
+The output will be something like:
+
+	Connected to fe80::7ced:d2ff:feee:e107%tapbr0 (fe80::7ced:d2ff:feee:e107), port 69
+	getting from fe80::7ced:d2ff:feee:e107%tapbr0:welcome.txt to welcome.txt [netascii]
+	Received 94 bytes in 0.0 seconds [113425 bit/s]
+
+The `-6` option is necessary to tell tftp to use IPv6 only and the `-v` is to tell the tftp client to be verbose.
+
+You should now see that the TFTP messages are received on the RIOT side. Opening a TFTP server on the Linux side is also possible. To do that, write down the IP address of the host (run on Linux):
+
+    ifconfig tapbr0
+    tapbr0    Link encap:Ethernet  HWaddr 0e:bc:0f:49:7f:e4
+              inet6 addr: fe80::cbc:fff:fe49:7fe4/64 Scope:Link
+              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
+              RX packets:22 errors:0 dropped:0 overruns:0 frame:0
+              TX packets:53 errors:0 dropped:0 overruns:0 carrier:0
+              collisions:0 txqueuelen:0
+              RX bytes:1928 (1.9 KB)  TX bytes:6217 (6.2 KB)
+
+And start the tftp server on Linux:
+
+	$ sudo /etc/init.d/tftpd-hpa stop && mkdir -p tftpserv && echo "hello world" > tftpserv/welcome.txt && sudo in.tftpd -vvv -L -6 -c -s -u ${USER} ./tftpserv
+
+Now, on the RIOT side, send a UDP packet using:
+
+    tftpc get welcome.txt octet 1 fe80::cbc:fff:fe49:7fe4
+
+You will get output that looks like this:
+
+    tftp_client: bin read welcome.txt:12
+
+    -- CLIENT DATA --
+    hello worl
+    -- CLIENT DATA --
+
+    -- CLIENT DATA --
+    d
+
+    -- CLIENT DATA --
+    tftp_client: SUCCESS: (null)
diff --git a/examples/gnrc_tftp/main.c b/examples/gnrc_tftp/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..e287aa21267be3e9ab936e19683fb20616f033eb
--- /dev/null
+++ b/examples/gnrc_tftp/main.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 Engineering-Spirit
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     examples
+ * @{
+ *
+ * @file
+ * @brief       Example application for demonstrating the RIOT TFTP stack
+ *
+ * @author      Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
+ *
+ * @}
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "shell.h"
+#include "msg.h"
+
+extern int tftp_client_cmd(int argc, char * *argv);
+extern int tftp_server_cmd(int argc, char * *argv);
+
+#define MAIN_QUEUE_SIZE     (4)
+static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];
+
+static const shell_command_t shell_commands[] = {
+    { "tftpc", "get/put data to a TFTP server",	 tftp_client_cmd },
+    { "tftps", "start and stop the TFTP server", tftp_server_cmd },
+    { NULL,	   NULL,							 NULL			 }
+};
+
+int main(void)
+{
+    /* we need a message queue for the thread running the shell in order to
+     * receive potentially fast incoming networking packets */
+    msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);
+    puts("RIOT TFTP stack example application");
+
+    /* start shell */
+    puts("All up, running the shell now");
+    char line_buf[SHELL_DEFAULT_BUFSIZE];
+
+    shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
+
+    /* should be never reached */
+    return 0;
+}
diff --git a/examples/gnrc_tftp/tftp_client.c b/examples/gnrc_tftp/tftp_client.c
new file mode 100644
index 0000000000000000000000000000000000000000..5f36fd93873c21f19e3166822f94feb3cf430472
--- /dev/null
+++ b/examples/gnrc_tftp/tftp_client.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2015 Engineering-Spirit
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     examples
+ * @{
+ *
+ * @file
+ * @brief       Demonstrating the sending and receiving of data via the TFTP client
+ *
+ * @author      Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
+ *
+ * @}
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "kernel.h"
+#include "net/gnrc/tftp.h"
+
+static const char *_tftp_default_host = "::1";
+
+/* default server text which can be received */
+static const char _tftp_client_hello[] = "Hello,\n"
+                                         "\n"
+                                         "Client text would also need to exist to be able to put data.\n"
+                                         "\n"
+                                         "Enjoy the RIOT-OS\n";
+
+static tftp_action_t _tftp_action;
+
+/**
+ * @brief called at every transaction start
+ */
+static bool _tftp_client_start_cb(tftp_action_t action, tftp_mode_t mode,
+                                  const char *file_name, size_t *len)
+{
+    /* translate the mode */
+    const char *str_mode = "ascii";
+
+    if (mode == TTM_OCTET) {
+        str_mode = "bin";
+    }
+    else if (mode == TTM_MAIL) {
+        str_mode = "mail";
+    }
+
+    /* translate the action */
+    const char *str_action = "read";
+    if (action == TFTP_WRITE) {
+        str_action = "write";
+    }
+
+    /* display the action being performed */
+    printf("tftp_client: %s %s %s:%u\n", str_mode, str_action, file_name, *len);
+
+    /* return the length of the text, if this is an read action */
+    if (action == TFTP_READ) {
+        *len = sizeof(_tftp_client_hello);
+    }
+
+    /* remember the action of the current transfer */
+    _tftp_action = action;
+
+    /* we accept the transfer to take place so we return true */
+    return true;
+}
+
+/**
+ * @brief called to get or put data, depending on the mode received by `_tftp_start_cb(action, ...)`
+ */
+static int _tftp_client_data_cb(uint32_t offset, void *data, size_t data_len)
+{
+    char *c = (char *) data;
+
+    /* if we are reading return the part of the data that is being requested */
+    if (_tftp_action == TFTP_WRITE) {
+        /* calculate the length of the data block to transfer */
+        if (offset + data_len > sizeof(_tftp_client_hello)) {
+            data_len -= (offset + data_len) - sizeof(_tftp_client_hello);
+        }
+
+        /* copy the block to the output buffer */
+        memcpy(data, _tftp_client_hello + offset, data_len);
+    }
+    else {
+        /* we received a data block which we output to the console */
+        printf("\n -- CLIENT DATA --\n%.*s\n -- CLIENT DATA --\n", data_len, c);
+    }
+
+    /* return the length of the data block */
+    return data_len;
+}
+
+/**
+ * @brief the transfer has stopped, see the event argument to determined if it was successful
+ *        or not.
+ */
+static void _tftp_client_stop_cb(tftp_event_t event, const char *msg)
+{
+    /* decode the stop event received */
+    const char *cause = "UNKOWN";
+
+    if (event == TFTP_SUCCESS) {
+        cause = "SUCCESS";
+    }
+    else if (event == TFTP_PEER_ERROR) {
+        cause = "ERROR From Client";
+    }
+    else if (event == TFTP_INTERN_ERROR) {
+        cause = "ERROR Internal Server Error";
+    }
+
+    /* print the transfer result to the console */
+    printf("tftp_client: %s: %s\n", cause, msg);
+}
+
+static int _tftp_client_cmd(int argc, char * *argv)
+{
+    ipv6_addr_t ip;
+    const char *file_name = argv[2];
+    tftp_mode_t mode = TTM_OCTET;
+    bool use_options = true;
+
+    ipv6_addr_from_str(&ip, _tftp_default_host);
+
+    if (argc >= 3 && argc <= 6) {
+        /* decode the action */
+        if (strcmp(argv[1], "get") == 0) {
+            _tftp_action = TFTP_READ;
+        }
+        else if (strcmp(argv[1], "put") == 0) {
+            _tftp_action = TFTP_WRITE;
+        }
+        else {
+            return -1;
+        }
+
+        /* get the transfer mode */
+        if (argc >= 4) {
+            if (strcmp(argv[3], "octet") == 0) {
+                mode = TTM_OCTET;
+            }
+            else if (strcmp(argv[3], "ascii") == 0) {
+                mode = TTM_ASCII;
+            }
+            else if (strcmp(argv[3], "mail") == 0) {
+                mode = TTM_MAIL;
+            }
+            else {
+                puts("tftp: couldn't parse the TFTP transfer mode");
+                return -1;
+            }
+        }
+
+        /* decode if we must use the TFTP option extension or not */
+        if (argc >= 5) {
+            if (strcmp(argv[4], "0") == 0) {
+                use_options = false;
+            }
+            else if (strcmp(argv[4], "1") == 0) {
+                use_options = true;
+            }
+            else {
+                puts("tftp: invalid options choose 0 or 1");
+                return -1;
+            }
+        }
+
+        /* decode the address */
+        if (argc >= 6) {
+            if (!ipv6_addr_from_str(&ip, argv[5])) {
+                puts("tftp: invalid IP address");
+                return -1;
+            }
+        }
+    }
+    else {
+        return -1;
+    }
+
+    if (_tftp_action == TFTP_READ) {
+        puts("tftp: starting read request");
+
+        gnrc_tftp_client_read(&ip, file_name, mode, _tftp_client_data_cb,
+                              _tftp_client_start_cb, _tftp_client_stop_cb, use_options);
+    }
+    else if (_tftp_action == TFTP_WRITE) {
+        puts("tftp: starting write request");
+
+        gnrc_tftp_client_write(&ip, file_name, mode, _tftp_client_data_cb,
+                               sizeof(_tftp_client_hello), _tftp_client_stop_cb, use_options);
+    }
+
+    return 0;
+}
+
+/**
+ * @brief start the TFTP server by creating a thread
+ */
+int tftp_client_cmd(int argc, char * *argv)
+{
+    if (_tftp_client_cmd(argc, argv) < 0) {
+        printf("usage: %s <action[get|put]> <file_name> <mode[ascii | octet(default) | mail]>\n"
+               "\t<use_options[1 (default) | 0]> <addr(default: ::1)>\n", argv[0]);
+    }
+
+    return 0;
+}
diff --git a/examples/gnrc_tftp/tftp_server.c b/examples/gnrc_tftp/tftp_server.c
new file mode 100644
index 0000000000000000000000000000000000000000..b825b0831df950def595464b7f5ea1caaf3820fd
--- /dev/null
+++ b/examples/gnrc_tftp/tftp_server.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 Engineering-Spirit
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     examples
+ * @{
+ *
+ * @file
+ * @brief       Demonstrating the sending and receiving of data via the TFTP server
+ *
+ * @author      Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
+ *
+ * @}
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "thread.h"
+#include "net/gnrc/tftp.h"
+
+/* the message queues */
+#define TFTP_QUEUE_SIZE     (4)
+static msg_t _tftp_msg_queue[TFTP_QUEUE_SIZE];
+
+/* allocate the stack */
+char _tftp_stack[THREAD_STACKSIZE_MAIN + THREAD_EXTRA_STACKSIZE_PRINTF];
+
+/* default server text which can be received */
+static const char _tftp_server_hello[] = "Hello world,\n"
+                                         "\n"
+                                         "Welcome to the wonderful world of IoT and embedded systems.\n"
+                                         "\n"
+                                         "Enjoy the RIOT-OS\n";
+
+static tftp_action_t _tftp_action;
+
+/**
+ * @brief called at every transcation start
+ */
+static bool _tftp_server_start_cb(tftp_action_t action, tftp_mode_t mode,
+                                  const char *file_name, size_t *len)
+{
+    /* translate the mode */
+    const char *str_mode = "ascii";
+
+    if (mode == TTM_OCTET) {
+        str_mode = "bin";
+    }
+    else if (mode == TTM_MAIL) {
+        str_mode = "mail";
+    }
+
+    /* translate the action */
+    const char *str_action = "read";
+    if (action == TFTP_WRITE) {
+        str_action = "write";
+    }
+
+    /* display the action being performed */
+    printf("tftp_server: %s %s %s:%u\n", str_mode, str_action, file_name, *len);
+
+    /* return the length of the text, if this is an read action */
+    if (action == TFTP_READ) {
+        *len = sizeof(_tftp_server_hello);
+    }
+
+    /* remember the action of the current transfer */
+    _tftp_action = action;
+
+    /* we accept the transfer to take place so we return true */
+    return true;
+}
+
+/**
+ * @brief called to get or put data, depending on the mode received by `_tftp_start_cb(action, ...)`
+ */
+static int _tftp_server_data_cb(uint32_t offset, void *data, size_t data_len)
+{
+    char *c = (char *) data;
+
+    /* if we are reading return the part of the data that is being requested */
+    if (_tftp_action == TFTP_READ) {
+        /* calculate the length of the data block to transfer */
+        if (offset + data_len > sizeof(_tftp_server_hello)) {
+            data_len -= (offset + data_len) - sizeof(_tftp_server_hello);
+        }
+
+        /* copy the block to the output buffer */
+        memcpy(data, _tftp_server_hello + offset, data_len);
+    }
+    else {
+        /* we received a data block which we output to the console */
+        printf("\n -- SERVER DATA --\n%.*s\n -- SERVER DATA --\n", data_len, c);
+    }
+
+    /* return the length of the data block */
+    return data_len;
+}
+
+/**
+ * @brief the transfer has stopped, see the event argument to determined if it was successful
+ *        or not.
+ */
+static void _tftp_server_stop_cb(tftp_event_t event, const char *msg)
+{
+    /* decode the stop event received */
+    const char *cause = "UNKOWN";
+
+    if (event == TFTP_SUCCESS) {
+        cause = "SUCCESS";
+    }
+    else if (event == TFTP_PEER_ERROR) {
+        cause = "ERROR From Client";
+    }
+    else if (event == TFTP_INTERN_ERROR) {
+        cause = "ERROR Internal Server Error";
+    }
+
+    /* print the transfer result to the console */
+    printf("tftp_server: %s: %s\n", cause, (msg == NULL) ? "NULL" : msg);
+}
+
+/**
+ * @brief the TFTP server thread
+ */
+void *tftp_server_wrapper(void *arg)
+{
+    (void)arg;
+
+    /* A message queue is needed to register for incoming packets */
+    msg_init_queue(_tftp_msg_queue, TFTP_QUEUE_SIZE);
+
+    /* inform the user */
+    puts("tftp_server: Starting TFTP service at port 69");
+
+    /* run the TFTP server */
+    gnrc_tftp_server(_tftp_server_data_cb, _tftp_server_start_cb, _tftp_server_stop_cb, true);
+
+    /* the TFTP server has been stopped */
+    puts("tftp_server: Stopped TFTP service");
+
+    return NULL;
+}
+
+/**
+ * @brief start the TFTP server by creating a thread
+ */
+void tftp_server_start(void)
+{
+    thread_create(_tftp_stack, sizeof(_tftp_stack),
+                  1, CREATE_WOUT_YIELD | CREATE_STACKTEST,
+                  tftp_server_wrapper, NULL, "TFTP Server");
+}
+
+/**
+ * @brief stop the TFTP server by sending a message to the thread
+ */
+void tftp_server_stop(void)
+{
+    gnrc_tftp_server_stop();
+}
+
+int tftp_server_cmd(int argc, char * *argv)
+{
+    switch (argc) {
+        case 2:
+            if (strcmp(argv[1], "start") == 0) {
+                tftp_server_start();
+                return 0;
+            }
+            else if (strcmp(argv[1], "stop") == 0) {
+                tftp_server_stop();
+                return 0;
+            }
+        /* no break */
+
+        default:
+            printf("usage: %s [start|stop]\n", argv[0]);
+            return 0;
+    }
+
+    return 0;
+}
diff --git a/sys/include/net/gnrc/tftp.h b/sys/include/net/gnrc/tftp.h
new file mode 100644
index 0000000000000000000000000000000000000000..13337e1a29f123471f9412eb9d72f4f7f7a75487
--- /dev/null
+++ b/sys/include/net/gnrc/tftp.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License v2.1. See the file LICENSE in the top level directory for
+ * more details.
+ */
+
+/**
+ * @defgroup    net_gnrc_tftp  TFTP Support Library
+ * @ingroup     net_gnrc
+ * @brief       Add's support for TFTP protocol parsing
+ * @{
+ *
+ * @file
+ * @brief       TFTP support library
+ *
+ * The TFTP module add's support for the TFTP protocol.
+ * It implements the following RFC's:
+ *  - https://tools.ietf.org/html/rfc1350
+ *     (RFC1350 The TFTP Protocol (Revision 2)
+ *
+ *  - https://tools.ietf.org/html/rfc2347
+ *     (RFC2347 TFTP Option Extension)
+ *
+ *  - https://tools.ietf.org/html/rfc2348
+ *     (RFC2348 TFTP Blocksize Option)
+ *
+ *  - https://tools.ietf.org/html/rfc2349
+ *     (RFC2349 TFTP Timeout Interval and Transfer Size Options)
+ *
+ * @author      Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
+ */
+
+#ifndef GNRC_TFTP_H_
+#define GNRC_TFTP_H_
+
+#include <inttypes.h>
+
+#include "byteorder.h"
+#include "kernel_types.h"
+#include "net/ipv6/addr.h"
+#include "net/gnrc/nettype.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief The maximum allowed length of the transfer filename
+ */
+#ifndef GNRC_TFTP_MAX_FILENAME_LEN
+#define GNRC_TFTP_MAX_FILENAME_LEN          (64)
+#endif
+
+/**
+ * @brief The base source port to be used by TFTP
+ */
+#ifndef GNRC_TFTP_DEFAULT_SRC_PORT
+#define GNRC_TFTP_DEFAULT_SRC_PORT          (10690)
+#endif
+
+/**
+ * @brief The default destination port of the TFTP server
+ */
+#ifndef GNRC_TFTP_DEFAULT_DST_PORT
+#define GNRC_TFTP_DEFAULT_DST_PORT          (69)
+#endif
+
+/**
+ * @brief The maximum allowed data bytes in the data packet
+ */
+#ifndef GNRC_TFTP_MAX_TRANSFER_UNIT
+#define GNRC_TFTP_MAX_TRANSFER_UNIT         (512)
+#endif
+
+/**
+ * @brief The number of retries that must be made before stopping a transfer
+ */
+#ifndef GNRC_TFTP_MAX_RETRIES
+#define GNRC_TFTP_MAX_RETRIES               (5)
+#endif
+
+/**
+ * @brief The default timeout of a data packet
+ */
+#ifndef GNRC_TFTP_DEFAULT_TIMEOUT
+#define GNRC_TFTP_DEFAULT_TIMEOUT           (1 * SEC_IN_USEC)
+#endif
+
+/**
+ * @brief TFTP action to perform
+ */
+typedef enum {
+    TFTP_READ,
+    TFTP_WRITE
+} tftp_action_t;
+
+/**
+ * @brief TFTP Transfer modes
+ */
+typedef enum {
+    TTM_ASCII,
+    TTM_OCTET,
+    TTM_MAIL
+} tftp_mode_t;
+
+/**
+ * @brief TFTP stop / finish events
+ */
+typedef enum {
+    TFTP_SUCCESS,           /**< The transfer was successful */
+    TFTP_PEER_ERROR,        /**< The peer send the given error */
+    TFTP_INTERN_ERROR       /**< There was an internal error */
+} tftp_event_t;
+
+/**
+ * @brief   callback define which is called when a new server request is placed
+ *          or when an client read request is made and the data length option is received
+ *
+ * @param [in] action       The action the transfer want to perform
+ * @param [in] mode         The data mode of the transfer
+ * @param [in] file_name    The filename of the file being transfered
+ * @param [in/out] data_len When a read action is performed, the application must give
+ *                          the total transfer size of the data. When a write action
+ *                          is performed the total transfer size will be given.
+ */
+typedef bool (*tftp_start_cb_t)(tftp_action_t action, tftp_mode_t mode,
+                                const char *file_name, size_t *data_len);
+
+/**
+ * @brief   callback define which is called to get or set data from/to the user application
+ */
+typedef int (*tftp_data_cb_t)(uint32_t offset, void *data, size_t data_len);
+
+/**
+ * @brief   callback define which is called when an transfer is stopped
+ */
+typedef void (*tftp_stop_cb_t)(tftp_event_t event, const char *msg);
+
+/**
+ * @brief Start the TFTP server
+ *
+ * @param [in] data_cb      called for each read data block
+ * @param [in] start_cb     called if a new client connection is requested
+ * @param [in] stop_cb      called if the transfer has finished
+ * @param [in] use_options  when set the client uses the option extensions
+ *
+ * @return 1 on success
+ * @return -1 on failure
+ */
+int gnrc_tftp_server(tftp_data_cb_t data_cb, tftp_start_cb_t start_cb, tftp_stop_cb_t stop_cb, bool use_options);
+
+/**
+ * @brief Stop the TFTP server
+ *
+ * @return 1 on success
+ * @return -1 on failure
+ */
+int gnrc_tftp_server_stop(void);
+
+/**
+ * @brief Start an TFTP client read action from the given destination
+ *
+ * @param [in] addr         the address of the server
+ * @param [in] file_name    the filename of the file to get
+ * @param [in] mode         the transfer mode
+ * @param [in] data_cb      called for each read data block
+ * @param [in] start_cb     called if the server returns the transfer_size option
+ * @param [in] stop_cb      called if the transfer has finished
+ * @param [in] use_option   when set the client uses the option extensions
+ *
+ * @return 1 on success
+ * @return -1 on failure
+ */
+int gnrc_tftp_client_read(ipv6_addr_t *addr, const char *file_name, tftp_mode_t mode,
+                          tftp_data_cb_t data_cb, tftp_start_cb_t start_cb, tftp_stop_cb_t stop_cb,
+                          bool use_option);
+
+/**
+ * @brief Start an TFTP client write action to the given destination
+ *
+ * @param [in] addr         the address of the server
+ * @param [in] file_name    the filename of the file to write
+ * @param [in] mode         the transfer mode
+ * @param [in] data_cb      called to store the received block
+ * @param [in] total_size   the total size of the transfer
+ * @param [in] stop_cb      called if the server returns the transfer_size option
+ * @param [in] use_option   when set the client uses the option extensions
+ *
+ * @return 1 on success
+ * @return -1 on failure
+ */
+int gnrc_tftp_client_write(ipv6_addr_t *addr, const char *file_name, tftp_mode_t mode,
+                           tftp_data_cb_t data_cb, size_t total_size, tftp_stop_cb_t stop_cb,
+                           bool use_option);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GNRC_TFTP_H_ */
+
+/**
+ * @}
+ */
diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile
index 6ac2001c7a705084ec9aa167c0242e691b0d1512..54fe9e810768a3e88145264354e3382342c50d67 100644
--- a/sys/net/gnrc/Makefile
+++ b/sys/net/gnrc/Makefile
@@ -109,5 +109,8 @@ endif
 ifneq (,$(filter gnrc_zep,$(USEMODULE)))
     DIRS += application_layer/zep
 endif
+ifneq (,$(filter gnrc_tftp,$(USEMODULE)))
+    DIRS += application_layer/tftp
+endif
 
 include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/gnrc/application_layer/tftp/Makefile b/sys/net/gnrc/application_layer/tftp/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4232bae24fff702f4636b64d6488ddabb0a0b2dd
--- /dev/null
+++ b/sys/net/gnrc/application_layer/tftp/Makefile
@@ -0,0 +1,3 @@
+MODULE = gnrc_tftp
+
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/gnrc/application_layer/tftp/gnrc_tftp.c b/sys/net/gnrc/application_layer/tftp/gnrc_tftp.c
new file mode 100644
index 0000000000000000000000000000000000000000..30bc583b8a7379e49e00e9428c1452a9f0fb30db
--- /dev/null
+++ b/sys/net/gnrc/application_layer/tftp/gnrc_tftp.c
@@ -0,0 +1,1139 @@
+/*
+ * Copyright (C) 2015 Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License v2.1. See the file LICENSE in the top level directory for
+ * more details.
+ */
+
+/**
+ * @ingroup net_gnrc_tftp
+ * @{
+ *
+ * @file
+ *
+ * @author      Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "net/gnrc/tftp.h"
+#include "net/gnrc/netapi.h"
+#include "net/gnrc/netreg.h"
+#include "net/gnrc/udp.h"
+#include "net/gnrc/ipv6.h"
+#include "random.h"
+
+#define ENABLE_DEBUG                (1)
+#include "debug.h"
+
+#if ENABLE_DEBUG
+/* For PRIu16 etc. */
+#include <inttypes.h>
+#endif
+
+static kernel_pid_t _tftp_kernel_pid;
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define CT_HTONS(x)                 ((uint16_t)((          \
+                                                    (((uint16_t)(x)) >> 8) & 0x00FF) |  \
+                                                ((((uint16_t)(x)) << 8) & 0xFF00)))
+#else
+#define CT_HTONS(x)                 ((uint16_t)x)
+#endif
+
+#define MIN(a, b)                    ((a) > (b) ? (b) : (a))
+#define ARRAY_LEN(x)                (sizeof(x) / sizeof(x[0]))
+
+#define TFTP_TIMEOUT_MSG            0x4000
+#define TFTP_STOP_SERVER_MSG        0x4001
+#define TFTP_DEFAULT_DATA_SIZE      (GNRC_TFTP_MAX_TRANSFER_UNIT    \
+                                     + sizeof(tftp_packet_data_t))
+
+/**
+ * @brief TFTP mode help support
+ */
+#define MODE(mode)                  { # mode, sizeof(# mode) }
+
+typedef struct {
+    char *name;
+    uint8_t len;
+} tftp_opt_t;
+
+/**
+ * @brief TFTP opcodes
+ * @{
+ */
+typedef uint16_t tftp_opcodes_t;
+
+#define TO_RRQ          CT_HTONS(1)         /**< Read Request */
+#define TO_WRQ          CT_HTONS(2)         /**< Write Request */
+#define TO_DATA         CT_HTONS(3)         /**< Data */
+#define TO_ACK          CT_HTONS(4)         /**< Acknowledgment */
+#define TO_ERROR        CT_HTONS(5)         /**< Error */
+#define TO_OACK         CT_HTONS(6)         /**< Option ACK */
+/**
+ * @}
+ */
+
+/**
+ * @brief TFTP Error Codes
+ * @{
+ */
+typedef uint16_t tftp_err_codes_t;
+
+#define TE_UN_DEF       CT_HTONS(0)         /**< Not defined, see error message */
+#define TE_NO_FILE      CT_HTONS(1)         /**< File not found */
+#define TE_ACCESS       CT_HTONS(2)         /**< Access violation */
+#define TE_DFULL        CT_HTONS(3)         /**< Disk full or allocation exceeded */
+#define TE_ILLOPT       CT_HTONS(4)         /**< Illegal TFTP operation */
+#define TE_UNKOWN_ID    CT_HTONS(5)         /**< Unknown transfer ID */
+#define TE_EXISTS       CT_HTONS(6)         /**< File already exists */
+#define TE_UNKOWN_USR   CT_HTONS(7)         /**< No such user */
+/**
+ * @}
+ */
+
+/* ordered as @see tftp_mode_t */
+tftp_opt_t _tftp_modes[] = {
+    [TTM_ASCII] = MODE(netascii),
+    [TTM_OCTET] = MODE(octet),
+    [TTM_MAIL]  = MODE(mail),
+};
+
+/**
+ * @brief TFTP Options
+ */
+typedef enum {
+    TOPT_BLKSIZE,
+    TOPT_TIMEOUT,
+    TOPT_TSIZE,
+} tftp_options_t;
+
+/* ordered as @see tftp_options_t */
+tftp_opt_t _tftp_options[] = {
+    [TOPT_BLKSIZE] = MODE(blksize),
+    [TOPT_TIMEOUT] = MODE(timeout),
+    [TOPT_TSIZE]   = MODE(tsize),
+};
+
+/**
+ * @brief The TFTP state
+ */
+typedef enum {
+    TS_FAILED      = -1,
+    TS_BUSY        = 0,
+    TS_FINISHED    = 1
+} tftp_state;
+
+/**
+ * @brief The type of the context used
+ */
+typedef enum {
+    CT_SERVER,
+    CT_CLIENT
+} tftp_context_type;
+
+/**
+ * @brief The TFTP context for the current transfer
+ */
+typedef struct {
+    char file_name[GNRC_TFTP_MAX_FILENAME_LEN];
+    tftp_mode_t mode;
+    tftp_opcodes_t op;
+    ipv6_addr_t peer;
+    xtimer_t timer;
+    msg_t timer_msg;
+    uint32_t timeout;
+    uint16_t dst_port;
+    uint16_t src_port;
+    tftp_context_type ct;
+    tftp_start_cb_t start_cb;
+    tftp_data_cb_t data_cb;
+    tftp_stop_cb_t stop_cb;
+    gnrc_netreg_entry_t entry;
+
+    /* transfer parameters */
+    uint16_t block_nr;
+    uint16_t block_size;
+    size_t transfer_size;
+    uint32_t block_timeout;
+    uint32_t retries;
+    bool use_options;
+    bool enable_options;
+    bool write_finished;
+} tftp_context_t;
+
+/**
+ * @brief The default TFTP header
+ */
+typedef struct __attribute__((packed)) {
+    tftp_opcodes_t opc;
+    uint8_t data[];
+} tftp_header_t;
+
+/**
+ * @brief The TFTP data packet
+ */
+typedef struct __attribute__((packed)) {
+    tftp_opcodes_t opc;
+    network_uint16_t block_nr;
+    uint8_t data[];
+} tftp_packet_data_t;
+
+/**
+ * @brief The TFTP error packet
+ */
+typedef struct __attribute__((packed)) {
+    tftp_opcodes_t opc;
+    tftp_err_codes_t err_code;
+    char err_msg[];
+} tftp_packet_error_t;
+
+/* get the TFTP opcode */
+static inline tftp_opcodes_t _tftp_parse_type(uint8_t *buf)
+{
+    return ((tftp_header_t *)buf)->opc;
+}
+
+/* initialize the context to it's default state */
+static int _tftp_init_ctxt(ipv6_addr_t *addr, const char *file_name,
+                           tftp_opcodes_t op, tftp_mode_t mode, tftp_context_type type,
+                           tftp_start_cb_t start, tftp_stop_cb_t stop,
+                           tftp_data_cb_t data, bool enable_options, tftp_context_t *ctxt);
+
+/* set the default TFTP options */
+static void _tftp_set_default_options(tftp_context_t *ctxt);
+
+/* set the TFTP options to use */
+static int _tftp_set_opts(tftp_context_t *ctxt, size_t blksize, uint32_t timeout, size_t total_size);
+
+/* this function registers the UDP port and won't return till the TFTP transfer is finished */
+static int _tftp_do_client_transfer(tftp_context_t *ctxt);
+
+/* the state process of the TFTP transfer */
+static tftp_state _tftp_state_processes(tftp_context_t *ctxt, msg_t *m);
+
+/* send an start request if we run in client mode */
+static tftp_state _tftp_send_start(tftp_context_t *ctxt, gnrc_pktsnip_t *buf);
+
+/* send data or and ack depending if we are reading or writing */
+static tftp_state _tftp_send_dack(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, tftp_opcodes_t op);
+
+/* send and TFTP error to the client */
+static tftp_state _tftp_send_error(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, tftp_err_codes_t err, const char *err_msg);
+
+/* this function sends the actual packet */
+static tftp_state _tftp_send(gnrc_pktsnip_t *buf, tftp_context_t *ctxt, size_t len);
+
+/* decode the default TFTP start packet */
+static int _tftp_decode_start(tftp_context_t *ctxt, uint8_t *buf, gnrc_pktsnip_t *outbuf);
+
+/* decode the TFTP option extensions */
+static int _tftp_decode_options(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, uint32_t start);
+
+/* decode the received ACK packet */
+static bool _tftp_validate_ack(tftp_context_t *ctxt, uint8_t *buf);
+
+/* processes the received data packet and calls the callback defined by the user */
+static int _tftp_process_data(tftp_context_t *ctxt, gnrc_pktsnip_t *buf);
+
+/* decode the received error packet and calls the callback defined by the user */
+static int _tftp_decode_error(uint8_t *buf, tftp_err_codes_t *err, const char * *err_msg);
+
+/* TFTP super loop server */
+static int _tftp_server(tftp_context_t *ctxt);
+
+/* get the maximum allowed transfer unit to avoid 6Lo fragmentation */
+static uint16_t _tftp_get_maximum_block_size(void)
+{
+    uint16_t tmp;
+    kernel_pid_t ifs[GNRC_NETIF_NUMOF];
+    size_t ifnum = gnrc_netif_get(ifs);
+
+    if (ifnum > 0 && gnrc_netapi_get(ifs[0], NETOPT_MAX_PACKET_SIZE, 0, &tmp, sizeof(uint16_t)) >= 0) {
+        /* TODO calculate proper block size */
+        return tmp - sizeof(udp_hdr_t) - sizeof(ipv6_hdr_t) - 10;
+    }
+
+    return GNRC_TFTP_MAX_TRANSFER_UNIT;
+}
+
+int gnrc_tftp_client_read(ipv6_addr_t *addr, const char *file_name, tftp_mode_t mode,
+                          tftp_data_cb_t data_cb, tftp_start_cb_t start_cb, tftp_stop_cb_t stop_cb, bool use_option_extensions)
+{
+    tftp_context_t ctxt;
+
+    assert(data_cb);
+    assert(start_cb);
+    assert(stop_cb);
+
+    /* prepare the context */
+    if (_tftp_init_ctxt(addr, file_name, TO_RRQ, mode, CT_CLIENT, start_cb, stop_cb, data_cb, use_option_extensions, &ctxt) != TS_FINISHED) {
+        return -EINVAL;
+    }
+
+    /* set the transfer options */
+    uint16_t mtu = _tftp_get_maximum_block_size();
+    if (!use_option_extensions ||
+        _tftp_set_opts(&ctxt, mtu, GNRC_TFTP_DEFAULT_TIMEOUT, 0) != TS_FINISHED) {
+        _tftp_set_default_options(&ctxt);
+
+        if (use_option_extensions) {
+            return -EINVAL;
+        }
+    }
+
+    /* start the process */
+    int ret = _tftp_do_client_transfer(&ctxt);
+
+    /* remove possibly stale timer */
+    xtimer_remove(&(ctxt.timer));
+
+    return ret;
+}
+
+int gnrc_tftp_client_write(ipv6_addr_t *addr, const char *file_name, tftp_mode_t mode,
+                           tftp_data_cb_t data_cb, size_t total_size, tftp_stop_cb_t stop_cb, bool use_option_extensions)
+{
+    tftp_context_t ctxt;
+
+    assert(data_cb);
+    assert(stop_cb);
+
+    /* prepare the context */
+    if (_tftp_init_ctxt(addr, file_name, TO_WRQ, mode, CT_CLIENT, NULL, stop_cb, data_cb, use_option_extensions, &ctxt) < 0) {
+        return -EINVAL;
+    }
+
+    /* set the transfer options */
+    uint16_t mtu = _tftp_get_maximum_block_size();
+    if (!use_option_extensions ||
+        _tftp_set_opts(&ctxt, mtu, GNRC_TFTP_DEFAULT_TIMEOUT, total_size) != TS_FINISHED) {
+
+        _tftp_set_default_options(&ctxt);
+
+        if (use_option_extensions) {
+            return -EINVAL;
+        }
+    }
+
+    /* start the process */
+    int ret = _tftp_do_client_transfer(&ctxt);
+
+    /* remove possibly stale timer */
+    xtimer_remove(&(ctxt.timer));
+
+    return ret;
+}
+
+int _tftp_init_ctxt(ipv6_addr_t *addr, const char *file_name,
+                    tftp_opcodes_t op, tftp_mode_t mode, tftp_context_type type,
+                    tftp_start_cb_t start, tftp_stop_cb_t stop,
+                    tftp_data_cb_t data, bool enable_options, tftp_context_t *ctxt)
+{
+
+    if (!addr) {
+        return TS_FAILED;
+    }
+
+    memset(ctxt, 0, sizeof(*ctxt));
+
+    /* set the default context parameters */
+    ctxt->op = op;
+    ctxt->ct = type;
+    ctxt->data_cb = data;
+    ctxt->start_cb = start;
+    ctxt->stop_cb = stop;
+    memcpy(&(ctxt->peer), addr, sizeof(ctxt->peer));
+    ctxt->mode = mode;
+    if (file_name) {
+        strncpy(ctxt->file_name, file_name, GNRC_TFTP_MAX_FILENAME_LEN);
+    }
+    ctxt->file_name[GNRC_TFTP_MAX_FILENAME_LEN - 1] = 0;
+    ctxt->dst_port = GNRC_TFTP_DEFAULT_DST_PORT;
+    ctxt->enable_options = enable_options;
+
+    /* transport layer parameters */
+    ctxt->block_size = GNRC_TFTP_MAX_TRANSFER_UNIT;
+    ctxt->block_timeout = GNRC_TFTP_DEFAULT_TIMEOUT;
+    ctxt->write_finished = false;
+
+    /* generate a random source UDP source port */
+    do {
+        ctxt->src_port = (genrand_uint32() & 0xff) + GNRC_TFTP_DEFAULT_SRC_PORT;
+    } while (gnrc_netreg_num(GNRC_NETTYPE_UDP, ctxt->src_port));
+
+    return TS_FINISHED;
+}
+
+void _tftp_set_default_options(tftp_context_t *ctxt)
+{
+    ctxt->block_size = GNRC_TFTP_MAX_TRANSFER_UNIT;
+    ctxt->timeout = GNRC_TFTP_DEFAULT_TIMEOUT;
+    ctxt->block_timeout = GNRC_TFTP_DEFAULT_TIMEOUT;
+    ctxt->transfer_size = 0;
+    ctxt->use_options = false;
+}
+
+int _tftp_set_opts(tftp_context_t *ctxt, size_t blksize, uint32_t timeout, size_t total_size)
+{
+    if (blksize > GNRC_TFTP_MAX_TRANSFER_UNIT || !timeout) {
+        return TS_FAILED;
+    }
+
+    ctxt->block_size = blksize;
+    ctxt->timeout = timeout;
+    ctxt->block_timeout = timeout;
+    ctxt->transfer_size = total_size;
+    ctxt->use_options = true;
+
+    return TS_FINISHED;
+}
+
+int gnrc_tftp_server(tftp_data_cb_t data_cb, tftp_start_cb_t start_cb, tftp_stop_cb_t stop_cb, bool use_options)
+{
+    /* check if there is only one TFTP server running */
+    if (_tftp_kernel_pid != KERNEL_PID_UNDEF) {
+        DEBUG("tftp: only one TFTP server allowed\n");
+        return -1;
+    }
+
+    /* context will be initialized when a connection is established */
+    tftp_context_t ctxt;
+    ctxt.data_cb = data_cb;
+    ctxt.start_cb = start_cb;
+    ctxt.stop_cb = stop_cb;
+    ctxt.enable_options = use_options;
+
+    /* validate our arguments */
+    assert(data_cb);
+    assert(start_cb);
+    assert(stop_cb);
+
+    /* save our kernel PID */
+    _tftp_kernel_pid = thread_getpid();
+
+    /* start the server */
+    int ret = _tftp_server(&ctxt);
+
+    /* remove possibly stale timer */
+    xtimer_remove(&(ctxt.timer));
+
+    /* reset the kernel PID */
+    _tftp_kernel_pid = KERNEL_PID_UNDEF;
+
+    return ret;
+}
+
+int gnrc_tftp_server_stop(void)
+{
+    /* check if there is a server running */
+    if (_tftp_kernel_pid == KERNEL_PID_UNDEF) {
+        DEBUG("tftp: no TFTP server running\n");
+        return -1;
+    }
+
+    /* prepare the stop message */
+    msg_t m = {
+        thread_getpid(),
+        TFTP_STOP_SERVER_MSG,
+        { NULL }
+    };
+
+    /* send the stop message */
+    msg_send(&m, _tftp_kernel_pid);
+
+    return 0;
+}
+
+int _tftp_server(tftp_context_t *ctxt)
+{
+    msg_t msg;
+    bool active = true;
+    gnrc_netreg_entry_t entry = { NULL, GNRC_TFTP_DEFAULT_DST_PORT,
+                                  thread_getpid() };
+
+    while (active) {
+        int ret = TS_BUSY;
+        bool got_client = false;
+
+        /* register the servers main listening port */
+        if (gnrc_netreg_register(GNRC_NETTYPE_UDP, &entry)) {
+            DEBUG("tftp: error starting server.");
+            return TS_FAILED;
+        }
+
+        /* main processing loop */
+        while (ret == TS_BUSY) {
+            /* wait for a message */
+            msg_receive(&msg);
+
+            /* check if the server stop message has been received */
+            if (msg.type == TFTP_STOP_SERVER_MSG) {
+                active = false;
+                ret = TS_FAILED;
+                break;
+            }
+            else {
+                /* continue normal server opration */
+                DEBUG("tftp: message incoming\n");
+                ret = _tftp_state_processes(ctxt, &msg);
+
+                /* release packet if we received one */
+                if (msg.type == GNRC_NETAPI_MSG_TYPE_RCV) {
+                    gnrc_pktbuf_release((gnrc_pktsnip_t *) msg.content.ptr);
+                }
+            }
+
+            /* if we just accepted a client, disable the server main listening port */
+            if (!got_client) {
+                gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &entry);
+
+                if (ret == TS_BUSY) {
+                    got_client = true;
+                    DEBUG("tftp: connection established\n");
+                }
+            }
+        }
+
+        /* remove any stall timers */
+        xtimer_remove(&(ctxt->timer));
+
+        /* if the server transfer has finished, unregister the client dst port */
+        gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &(ctxt->entry));
+        DEBUG("tftp: connection terminated\n");
+    }
+
+    /* unregister our UDP listener on this thread */
+    gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &entry);
+
+    return 0;
+}
+
+int _tftp_do_client_transfer(tftp_context_t *ctxt)
+{
+    msg_t msg;
+    tftp_state ret = TS_BUSY;
+
+    /* register our DNS response listener */
+    gnrc_netreg_entry_t entry = { NULL, ctxt->src_port, thread_getpid() };
+
+    if (gnrc_netreg_register(GNRC_NETTYPE_UDP, &entry)) {
+        DEBUG("tftp: error starting server.");
+        return TS_FAILED;
+    }
+
+    /* try to start the TFTP transfer */
+    ret = _tftp_state_processes(ctxt, NULL);
+    if (ret != TS_BUSY) {
+        DEBUG("tftp: transfer failed\n");
+        /* if the start failed return */
+        return ret;
+    }
+
+    /* main processing loop */
+    while (ret == TS_BUSY) {
+        /* wait for a message */
+        msg_receive(&msg);
+        DEBUG("tftp: message received\n");
+        ret = _tftp_state_processes(ctxt, &msg);
+
+        /* release packet if we received one */
+        if (msg.type == GNRC_NETAPI_MSG_TYPE_RCV) {
+            gnrc_pktbuf_release((gnrc_pktsnip_t *) msg.content.ptr);
+        }
+    }
+
+    /* unregister our UDP listener on this thread */
+    gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &entry);
+
+    return ret;
+}
+
+tftp_state _tftp_state_processes(tftp_context_t *ctxt, msg_t *m)
+{
+    gnrc_pktsnip_t *outbuf = gnrc_pktbuf_add(NULL, NULL, TFTP_DEFAULT_DATA_SIZE,
+                                             GNRC_NETTYPE_UNDEF);
+
+    /* check if this is an client start */
+    if (!m) {
+        DEBUG("tftp: starting transaction as client\n");
+        return _tftp_send_start(ctxt, outbuf);
+    }
+    else if (m->type == TFTP_TIMEOUT_MSG) {
+        DEBUG("tftp: timeout occured\n");
+        if (++(ctxt->retries) > GNRC_TFTP_MAX_RETRIES) {
+            /* transfer failed due to lost peer */
+            DEBUG("tftp: peer lost\n");
+            gnrc_pktbuf_release(outbuf);
+            return TS_FAILED;
+        }
+
+        /* increase the timeout for congestion control */
+        ctxt->block_timeout <<= 1;
+
+        /* the send message timed out, re-sending */
+        if (ctxt->dst_port == GNRC_TFTP_DEFAULT_DST_PORT) {
+            DEBUG("tftp: sending timed out, re-sending\n");
+            /* we are still negotiating resent, start */
+            return _tftp_send_start(ctxt, outbuf);
+        }
+        else {
+            DEBUG("tftp: last data or ack packet lost, resending\n");
+            /* we are sending / receiving data */
+            /* if we are reading resent the ACK, if writing the DATA */
+            return _tftp_send_dack(ctxt, outbuf, (ctxt->op == TO_RRQ) ? TO_ACK : TO_DATA);
+        }
+    }
+    else if (m->type != GNRC_NETAPI_MSG_TYPE_RCV) {
+        DEBUG("tftp: unknown message");
+        gnrc_pktbuf_release(outbuf);
+        return TS_BUSY;
+    }
+
+    gnrc_pktsnip_t *pkt = (gnrc_pktsnip_t *)(m->content.ptr);
+
+    gnrc_pktsnip_t *tmp;
+    LL_SEARCH_SCALAR(pkt, tmp, type, GNRC_NETTYPE_UDP);
+    udp_hdr_t *udp = (udp_hdr_t *)tmp->data;
+
+    LL_SEARCH_SCALAR(pkt, tmp, type, GNRC_NETTYPE_IPV6);
+    ipv6_hdr_t *ip = (ipv6_hdr_t *)tmp->data;
+    uint8_t *data = (uint8_t *)pkt->data;
+
+    xtimer_remove(&(ctxt->timer));
+
+    switch (_tftp_parse_type(data)) {
+        case TO_RRQ:
+        case TO_WRQ: {
+            if (byteorder_ntohs(udp->dst_port) != GNRC_TFTP_DEFAULT_DST_PORT) {
+                /* not a valid start packet */
+                DEBUG("tftp: incoming packet on port %d dropped\n", byteorder_ntohs(udp->dst_port));
+                gnrc_pktbuf_release(outbuf);
+                return TS_FAILED;
+            }
+
+            /* reinitialize the context with the current client */
+            tftp_opcodes_t op = _tftp_parse_type(data);
+            _tftp_init_ctxt(&(ip->src), NULL, op, TTM_ASCII, CT_SERVER,
+                            ctxt->start_cb, ctxt->stop_cb, ctxt->data_cb,
+                            ctxt->enable_options, ctxt);
+
+            /* get the context of the client */
+            ctxt->dst_port = byteorder_ntohs(udp->src_port);
+            DEBUG("tftp: client's port is %" PRIu16 "\n", ctxt->dst_port);
+
+            int offset = _tftp_decode_start(ctxt, data, outbuf);
+            DEBUG("tftp: offset after decode start = %i\n", offset);
+            if (offset < 0) {
+                DEBUG("tftp: there is no data?\n");
+                gnrc_pktbuf_release(outbuf);
+                return TS_FAILED;
+            }
+
+            /* register a listener for the UDP port */
+            ctxt->entry.next = NULL;
+            ctxt->entry.demux_ctx = ctxt->src_port;
+            ctxt->entry.pid = thread_getpid();
+            gnrc_netreg_register(GNRC_NETTYPE_UDP, &(ctxt->entry));
+
+            /* try to decode the options */
+            tftp_state state;
+            tftp_opcodes_t opcode;
+            if (ctxt->enable_options &&
+                _tftp_decode_options(ctxt, pkt, offset) > offset) {
+                DEBUG("tftp: send option ACK\n");
+
+                /* the client send the TFTP options */
+                opcode = TO_OACK;
+            }
+            else {
+                DEBUG("tftp: send normal ACK\n");
+
+                /* the client didn't send options, use ACK and set defaults */
+                _tftp_set_default_options(ctxt);
+
+                /* send the first data block */
+                if (ctxt->op == TO_RRQ) {
+                    ++(ctxt->block_nr);
+                    opcode = TO_DATA;
+                }
+                else {
+                    opcode = TO_ACK;
+                }
+            }
+
+            /* validate if the application accepts the action, mode, filename and transfer_size */
+            tftp_action_t action = (ctxt->op == TO_RRQ) ? TFTP_READ : TFTP_WRITE;
+            if (!ctxt->start_cb(action, ctxt->mode, ctxt->file_name, &(ctxt->transfer_size))) {
+                _tftp_send_error(ctxt, outbuf, TE_ACCESS, "Blocked by user application");
+                DEBUG("tftp: callback not able to handle mode\n");
+                return TS_FAILED;
+            }
+
+            /* the client send the TFTP options */
+            state = _tftp_send_dack(ctxt, outbuf, opcode);
+
+            /* check if the client negotiation was successful */
+            if (state != TS_BUSY) {
+                DEBUG("tftp: not able to send ACK");
+            }
+            return state;
+        } break;
+
+        case TO_DATA: {
+            /* try to process the data */
+            int proc = _tftp_process_data(ctxt, pkt);
+            if (proc < 0) {
+                DEBUG("tftp: data not accepted\n");
+                /* the data is not accepted return */
+                gnrc_pktbuf_release(outbuf);
+                return TS_BUSY;
+            }
+
+            /* check if this is the first block */
+            if (!ctxt->block_nr
+                && ctxt->dst_port == GNRC_TFTP_DEFAULT_DST_PORT
+                && !ctxt->use_options) {
+                /* no OACK received, restore default TFTP parameters */
+                _tftp_set_default_options(ctxt);
+                DEBUG("tftp: restore default TFTP parameters\n");
+
+                /* switch the destination port to the src port of the server */
+                ctxt->dst_port = byteorder_ntohs(udp->src_port);
+            }
+
+            /* wait for the next data block */
+            DEBUG("tftp: wait for the next data block\n");
+            ++(ctxt->block_nr);
+            _tftp_send_dack(ctxt, outbuf, TO_ACK);
+
+            /* check if the data transfer has finished */
+            if (proc < ctxt->block_size) {
+                DEBUG("tftp: transfer finished\n");
+
+                if (ctxt->stop_cb) {
+                    ctxt->stop_cb(TFTP_SUCCESS, NULL);
+                }
+
+                return TS_FINISHED;
+            }
+
+            return TS_BUSY;
+        } break;
+
+        case TO_ACK: {
+            /* validate if this is the ACK we are waiting for */
+            if (!_tftp_validate_ack(ctxt, data)) {
+                /* invalid packet ACK, drop */
+                gnrc_pktbuf_release(outbuf);
+                return TS_BUSY;
+            }
+
+            /* check if the write action is finished */
+            if (ctxt->write_finished) {
+                gnrc_pktbuf_release(outbuf);
+
+                if (ctxt->stop_cb) {
+                    ctxt->stop_cb(TFTP_SUCCESS, NULL);
+                }
+
+                return TS_FINISHED;
+            }
+
+            /* check if this is the first ACK */
+            if (!ctxt->block_nr && ctxt->dst_port != byteorder_ntohs(udp->src_port)) {
+                /* no OACK received restore default TFTP parameters */
+                _tftp_set_default_options(ctxt);
+
+                /* switch the destination port to the src port of the server */
+                ctxt->dst_port = byteorder_ntohs(udp->src_port);
+            }
+
+            /* send the next data block */
+            ++(ctxt->block_nr);
+
+            return _tftp_send_dack(ctxt, outbuf, TO_DATA);
+        } break;
+
+        case TO_ERROR: {
+            tftp_err_codes_t err;
+            const char *err_msg;
+
+            /* decode the received error */
+            _tftp_decode_error(data, &err, &err_msg);
+
+            /* inform the user application about the error */
+            if (ctxt->stop_cb) {
+                ctxt->stop_cb(TFTP_PEER_ERROR, err_msg);
+            }
+
+            DEBUG("tftp: ERROR: %s\n", err_msg);
+            gnrc_pktbuf_release(outbuf);
+            return TS_FAILED;
+        } break;
+
+        case TO_OACK: {
+            /* only allow one OACK to be received */
+            if (ctxt->dst_port != byteorder_ntohs(udp->src_port)) {
+                DEBUG("tftp: TO_OACK received\n");
+
+                /* decode the options */
+                _tftp_decode_options(ctxt, pkt, 0);
+
+                /* take the new source port */
+                ctxt->dst_port = byteorder_ntohs(udp->src_port);
+
+                /* we must send block one to finish the negotiation in send mode */
+                if (ctxt->op == TO_WRQ) {
+                    ++(ctxt->block_nr);
+                }
+            }
+            else {
+                DEBUG("tftp: dropping double TO_OACK\n");
+            }
+
+            return _tftp_send_dack(ctxt, outbuf, (ctxt->op == TO_WRQ) ? TO_DATA : TO_ACK);
+        } break;
+    }
+
+    gnrc_pktbuf_release(outbuf);
+    return TS_FAILED;
+}
+
+size_t _tftp_add_option(uint8_t *dst, tftp_opt_t *opt, uint32_t value)
+{
+    size_t offset;
+
+    /* set the option name */
+    memcpy(dst, opt->name, opt->len);
+    offset = opt->len;
+
+    /* set the option value */
+    offset += sprintf((char *)(dst + opt->len), "%" PRIu32, value);
+
+    /* finish option value */
+    *(dst + offset) = 0;
+    return ++offset;
+}
+
+uint32_t _tftp_append_options(tftp_context_t *ctxt, tftp_header_t *hdr, uint32_t offset)
+{
+    offset += _tftp_add_option(hdr->data + offset, _tftp_options + TOPT_BLKSIZE, ctxt->block_size);
+    offset += _tftp_add_option(hdr->data + offset, _tftp_options + TOPT_TIMEOUT, (ctxt->timeout / SEC_IN_USEC));
+
+    /**
+     * Only set the transfer option if we are sending.
+     * Or when we are reading in bin mode.
+     */
+    if ((ctxt->ct == CT_SERVER && ctxt->op == TO_RRQ) ||
+        (ctxt->ct == CT_CLIENT && ctxt->op == TO_WRQ) ||
+        ctxt->mode == TTM_OCTET) {
+        offset += _tftp_add_option(hdr->data + offset, _tftp_options + TOPT_TSIZE, ctxt->transfer_size);
+    }
+
+    return offset;
+}
+
+tftp_state _tftp_send_start(tftp_context_t *ctxt, gnrc_pktsnip_t *buf)
+{
+    /* get required values */
+    int len = strlen(ctxt->file_name) + 1;          /* we also want the \0 char */
+    tftp_opt_t *m = _tftp_modes + ctxt->mode;
+
+    /* start filling the header */
+    tftp_header_t *hdr = (tftp_header_t *)(buf->data);
+
+    hdr->opc = ctxt->op;
+    memcpy(hdr->data, ctxt->file_name, len);
+    memcpy(hdr->data + len, m->name, m->len);
+
+    /* fill the options */
+    uint32_t offset = (len + m->len);
+    if (ctxt->use_options) {
+        offset = _tftp_append_options(ctxt, hdr, offset);
+    }
+
+    /* send the data */
+    return _tftp_send(buf, ctxt, offset + sizeof(tftp_header_t));
+}
+
+tftp_state _tftp_send_dack(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, tftp_opcodes_t op)
+{
+    size_t len = 0;
+
+    assert(op == TO_DATA || op == TO_ACK || op == TO_OACK);
+
+    /* fill the packet */
+    tftp_packet_data_t *pkt = (tftp_packet_data_t *)(buf->data);
+    pkt->block_nr = byteorder_htons(ctxt->block_nr);
+    pkt->opc = op;
+
+    if (op == TO_DATA) {
+        DEBUG("tftp: getting data from callback\n");
+        /* get the required data from the user */
+        len = ctxt->data_cb(ctxt->block_size * (ctxt->block_nr - 1), pkt->data, ctxt->block_size);
+
+        /* check if we are finished on ACK receive */
+        ctxt->write_finished = (len < ctxt->block_size);
+
+        /* enable timeout */
+        ctxt->block_timeout = ctxt->timeout;
+    }
+    else if (op == TO_OACK) {
+        /* append the options */
+        len = _tftp_append_options(ctxt, (tftp_header_t *)pkt, 0);
+
+        /* disable timeout*/
+        ctxt->block_timeout = 0;
+    }
+    else if (op == TO_ACK) {
+        /* disable timeout*/
+        ctxt->block_timeout = 0;
+    }
+
+    /* send the data */
+    return _tftp_send(buf, ctxt, sizeof(tftp_packet_data_t) + len);
+}
+
+tftp_state _tftp_send_error(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, tftp_err_codes_t err, const char *err_msg)
+{
+    int strl = err_msg
+               ? strlen(err_msg) + 1
+               : 0;
+
+    (void) ctxt;
+
+    /* fill the packet */
+    tftp_packet_error_t *pkt = (tftp_packet_error_t *)(buf->data);
+    pkt->opc = TO_ERROR;
+    pkt->err_code = err;
+    memcpy(pkt->err_msg, err_msg, strl);
+
+    /* don't set a timeout on the error */
+    ctxt->block_timeout = 0;
+
+    /* return the size of the packet */
+    _tftp_send(buf, ctxt, sizeof(tftp_packet_error_t) + strl);
+
+    return TS_FAILED;
+}
+
+tftp_state _tftp_send(gnrc_pktsnip_t *buf, tftp_context_t *ctxt, size_t len)
+{
+    network_uint16_t src_port, dst_port;
+    gnrc_pktsnip_t *udp, *ip;
+
+    assert(len <= TFTP_DEFAULT_DATA_SIZE);
+
+    /* down-size the packet to it's used size */
+    if (len > TFTP_DEFAULT_DATA_SIZE) {
+        DEBUG("tftp: can't reallocate to bigger packet, buffer overflowed");
+        gnrc_pktbuf_release(buf);
+
+        if (ctxt->stop_cb) {
+            ctxt->stop_cb(TFTP_INTERN_ERROR, "buffer overflowed");
+        }
+
+        return TS_FAILED;
+    }
+    else if (gnrc_pktbuf_realloc_data(buf, len) != 0) {
+        assert(false);
+
+        DEBUG("tftp: failed to reallocate data snippet");
+        gnrc_pktbuf_release(buf);
+
+        /* inform the user that we can't reallocate */
+        if (ctxt->stop_cb) {
+            ctxt->stop_cb(TFTP_INTERN_ERROR, "no reallocate");
+        }
+
+        return TS_FAILED;
+    }
+
+    /* allocate UDP header, set source port := destination port */
+    src_port.u16 = ctxt->src_port;
+    dst_port.u16 = ctxt->dst_port;
+    udp = gnrc_udp_hdr_build(buf, src_port.u8, sizeof(src_port),
+                             dst_port.u8, sizeof(dst_port));
+    if (udp == NULL) {
+        DEBUG("tftp: error unable to allocate UDP header");
+        gnrc_pktbuf_release(buf);
+
+        if (ctxt->stop_cb) {
+            ctxt->stop_cb(TFTP_INTERN_ERROR, "no udp allocate");
+        }
+
+        return TS_FAILED;
+    }
+
+    /* allocate IPv6 header */
+    ip = gnrc_ipv6_hdr_build(udp, NULL, 0, ctxt->peer.u8, sizeof(ipv6_addr_t));
+    if (ip == NULL) {
+        DEBUG("tftp: error unable to allocate IPv6 header");
+        gnrc_pktbuf_release(udp);
+
+        if (ctxt->stop_cb) {
+            ctxt->stop_cb(TFTP_INTERN_ERROR, "no ip allocate");
+        }
+
+        return TS_FAILED;
+    }
+
+    /* send packet */
+    if (gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, GNRC_NETREG_DEMUX_CTX_ALL,
+                                  ip) == 0) {
+        /* if send failed inform the user */
+        DEBUG("tftp: error unable to locate UDP thread");
+        gnrc_pktbuf_release(ip);
+
+        if (ctxt->stop_cb) {
+            ctxt->stop_cb(TFTP_INTERN_ERROR, "no dispatch send");
+        }
+
+        return TS_FAILED;
+    }
+
+    /* only set timeout if enabled for this block */
+    if (ctxt->block_timeout) {
+        ctxt->timer_msg.type = TFTP_TIMEOUT_MSG;
+        xtimer_set_msg(&(ctxt->timer), ctxt->block_timeout, &(ctxt->timer_msg), thread_getpid());
+        DEBUG("tftp: set timeout %" PRIu32 " ms\n", ctxt->block_timeout / MS_IN_USEC);
+    }
+
+    return TS_BUSY;
+}
+
+bool _tftp_validate_ack(tftp_context_t *ctxt, uint8_t *buf)
+{
+    tftp_packet_data_t *pkt = (tftp_packet_data_t *) buf;
+
+    return ctxt->block_nr == byteorder_ntohs(pkt->block_nr);
+}
+
+int _tftp_decode_start(tftp_context_t *ctxt, uint8_t *buf, gnrc_pktsnip_t *outbuf)
+{
+    /* decode the packet */
+    tftp_header_t *hdr = (tftp_header_t *)buf;
+
+    /* get the file name and copy terminating byte */
+    size_t fnlen = strlen((char *)hdr->data) + 1;
+
+    if (fnlen >= GNRC_TFTP_MAX_FILENAME_LEN) {
+        _tftp_send_error(ctxt, outbuf, TE_ILLOPT, "Filename to long");
+
+        if (ctxt->stop_cb) {
+            ctxt->stop_cb(TFTP_INTERN_ERROR, "Filename to long");
+        }
+
+        return TS_FAILED;
+    }
+    memcpy(ctxt->file_name, hdr->data, fnlen);
+
+    /* Get mode string by advancing pointer */
+    char *str_mode = (char *)hdr->data + fnlen;
+    DEBUG("tftp: incoming request '%s', mode: %s\n", ctxt->file_name, str_mode);
+
+    /* decode the TFTP transfer mode */
+    for (uint32_t idx = 0; idx < ARRAY_LEN(_tftp_modes); ++idx) {
+        if (memcmp(_tftp_modes[idx].name, str_mode, _tftp_modes[idx].len) == 0) {
+            ctxt->mode = (tftp_mode_t)idx;
+            return (str_mode + _tftp_modes[idx].len) - (char *)hdr->data;
+        }
+    }
+
+    return -EINVAL;
+}
+
+int _tftp_decode_options(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, uint32_t start)
+{
+    tftp_header_t *pkt = (tftp_header_t *)buf->data;
+    size_t offset = start;
+
+    DEBUG("tftp: decode options\n");
+    DEBUG("tftp:   buffer size = %d\n", buf->size);
+    while ((offset + sizeof(uint16_t)) < (buf->size)) {
+        DEBUG("tftp:   offset = %d\n", offset);
+        /* get the option name */
+        const char *name = (const char *)(pkt->data + offset);
+        offset += strlen(name) + 1;
+        /* get the value name */
+        const char *value = (const char *)(pkt->data + offset);
+        offset += strlen(value) + 1;
+
+        /* check what option we are parsing */
+        for (uint32_t idx = 0; idx < ARRAY_LEN(_tftp_options); ++idx) {
+            if (memcmp(name, _tftp_options[idx].name, _tftp_options[idx].len) == 0) {
+                /* set the option value of the known options */
+                switch (idx) {
+                    case TOPT_BLKSIZE:
+                        ctxt->block_size = atoi(value);
+                        DEBUG("tftp: got option TOPT_BLKSIZE = %" PRIu16 "\n", ctxt->block_size);
+                        break;
+
+                    case TOPT_TSIZE:
+                        ctxt->transfer_size = atoi(value);
+                        DEBUG("tftp: got option TOPT_TSIZE = %" PRIu32 "\n", (uint32_t)ctxt->transfer_size);
+
+                        if (ctxt->start_cb && ctxt->ct == CT_CLIENT) {
+                            ctxt->start_cb(TFTP_READ, ctxt->mode, ctxt->file_name, &(ctxt->transfer_size));
+                        }
+                        break;
+
+                    case TOPT_TIMEOUT:
+                        ctxt->timeout = atoi(value) * SEC_IN_USEC;
+                        DEBUG("tftp: option TOPT_TIMEOUT = %" PRIu32 " ms\n", ctxt->timeout / MS_IN_USEC);
+                        break;
+                }
+
+                break;
+            }
+        }
+    }
+
+    DEBUG("tftp:   return %d\n", offset);
+    return offset;
+}
+
+int _tftp_process_data(tftp_context_t *ctxt, gnrc_pktsnip_t *buf)
+{
+    tftp_packet_data_t *pkt = (tftp_packet_data_t *) buf->data;
+
+    DEBUG("tftp: processing data\n");
+
+    uint16_t block_nr = byteorder_ntohs(pkt->block_nr);
+
+    /* check if this is the packet we are waiting for */
+    if (block_nr != (ctxt->block_nr + 1)) {
+        DEBUG("tftp: not the packet we were wating for\n");
+        return TS_BUSY;
+    }
+
+    /* send the user data trough to the user application */
+    if (ctxt->data_cb(ctxt->block_nr * ctxt->block_size, pkt->data, buf->size - sizeof(tftp_packet_data_t)) < 0) {
+        DEBUG("tftp: error in data callback\n");
+        return TS_BUSY;
+    }
+
+    /* return the number of data bytes received */
+    return buf->size - sizeof(tftp_packet_data_t);
+}
+
+int _tftp_decode_error(uint8_t *buf, tftp_err_codes_t *err, const char * *err_msg)
+{
+    tftp_packet_error_t *pkt = (tftp_packet_error_t *) buf;
+
+    /* return the error code and message */
+    *err = pkt->err_code;
+    *err_msg = pkt->err_msg;
+
+    return sizeof(tftp_packet_error_t) + strlen(pkt->err_msg) + 1;
+}
+
+/**
+ * @}
+ */