diff --git a/CMakeLists.txt b/CMakeLists.txt
index 12c759a85a38fb029459d35f2a3174b66bcf4e0b..67542ca4c5c26761b6d3ab355418d266af7dc0d3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -196,6 +196,7 @@ add_library(beyond-protocol STATIC
     src/service.cpp
     src/codecs/golomb.cpp
     src/codecs/h264.cpp
+    src/codecs/data.cpp
 )
 
 target_include_directories(beyond-protocol PUBLIC
diff --git a/include/ftl/codec/data.hpp b/include/ftl/codec/data.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..916432d442962f1d99227d7cb5d775ec07e05103
--- /dev/null
+++ b/include/ftl/codec/data.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <vector>
+#include <string>
+#include <tuple>
+#include <cstdint>
+
+#include <ftl/data/camera.hpp>
+#include <ftl/protocol/channels.hpp>
+
+namespace ftl {
+namespace data {
+
+using Pose = std::vector<double>;
+using StereoPose = std::tuple<Pose, Pose>;
+using Intrinsics = std::tuple<ftl::data::Camera, int, int>;
+
+}
+namespace codec {
+
+template <typename T>
+void pack(const T &v, std::vector<uint8_t> &out);
+
+template <typename T>
+T unpack(const std::vector<uint8_t> &in);
+
+}
+}
diff --git a/include/ftl/codec/msgpack.hpp b/include/ftl/codec/msgpack.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4f8eba90a3b1b252b5ea2f447b3a59a73b823451
--- /dev/null
+++ b/include/ftl/codec/msgpack.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <msgpack.hpp>
+#include <ftl/codec/data.hpp>
+#include <ftl/utility/vectorbuffer.hpp>
+
+template <typename T>
+void ftl::codec::pack(const T &v, std::vector<uint8_t> &out) {
+    out.resize(0);
+    ftl::util::FTLVectorBuffer buf(out);
+    msgpack::pack(buf, v);
+}
+
+template <typename T>
+T ftl::codec::unpack(const std::vector<uint8_t> &in) {
+    auto unpacked = msgpack::unpack((const char*)in.data(), in.size());
+    T t;
+    unpacked.get().convert<T>(t);
+    return t;
+}
diff --git a/include/ftl/data/camera.hpp b/include/ftl/data/camera.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6a931fecf8e018abdfbebc2896c01f668d5008ec
--- /dev/null
+++ b/include/ftl/data/camera.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+namespace ftl {
+namespace data {
+
+/**
+ * All properties associated with cameras. This structure is designed to
+ * operate on CPU and GPU.
+ */
+struct Camera {
+    float fx;               // Focal length X
+    float fy;               // Focal length Y (usually same as fx)
+    float cx;               // Principle point Y
+    float cy;               // Principle point Y
+    unsigned int width;     // Pixel width
+    unsigned int height;    // Pixel height
+    float minDepth;         // Near clip in meters
+    float maxDepth;         // Far clip in meters
+    float baseline;         // For stereo pair
+    float doffs;            // Disparity offset
+};
+
+}  // namespace data
+}  // namespace ftl
diff --git a/include/ftl/data/capabilities.hpp b/include/ftl/data/capabilities.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f81f4c733625503bfd003ba8071a95610fd9556f
--- /dev/null
+++ b/include/ftl/data/capabilities.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+namespace ftl {
+namespace data {
+
+/**
+ * To be added to the capabilities channel to indicate what the source device
+ * is capable of. These properties should be features of the source that
+ * cannot be determined by simply checking for channels, and may include
+ * status information about processing that has been performed.
+ */
+enum class Capability : int {
+    MOVABLE = 0,  // Is a pose controllable camera
+    ACTIVE,     // An active depth sensor
+    VIDEO,      // Is video and not just static
+    ADJUSTABLE,  // Camera properties can be changed (exposure etc)
+    VIRTUAL,    // Is not a physical camera
+    TOUCH,      // Touch related feedback supported
+    VR,         // Is a VR device, so provides own active pose etc
+    LIVE,       // Live, not recorded (removed from ftl file sources)
+    FUSED,      // Reconstruction has been performed
+    STREAMED,   // Means it came from a stream and not device
+    EQUI_RECT,  // 360 rendered (Equirectangular Render)
+    STEREO      // Side-by-side stereo render
+};
+
+}  // namespace data
+}  // namespace ftl
diff --git a/include/ftl/utility/vectorbuffer.hpp b/include/ftl/utility/vectorbuffer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..104ca312ae35814bb24b35ea041719e629fe53f3
--- /dev/null
+++ b/include/ftl/utility/vectorbuffer.hpp
@@ -0,0 +1,29 @@
+/**
+ * @file vectorbuffer.hpp
+ * @copyright Copyright (c) 2020 University of Turku, MIT License
+ * @author Nicolas Pope
+ */
+
+#pragma once
+
+#include <vector>
+
+namespace ftl {
+namespace util {
+
+/**
+ * Used for msgpack encoding into an existing std::vector object.
+ */
+class FTLVectorBuffer {
+ public:
+    inline explicit FTLVectorBuffer(std::vector<unsigned char> &v) : vector_(v) {}
+
+    inline void write(const char *data, std::size_t size) {
+        vector_.insert(vector_.end(), (const unsigned char*)data, (const unsigned char*)data+size);
+    }
+
+ private:
+    std::vector<unsigned char> &vector_;
+};
+}  // namespace util
+}  // namespace ftl
diff --git a/src/codecs/data.cpp b/src/codecs/data.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ab136651e1aaf06dcfc7d61b01fc1d4eea643c68
--- /dev/null
+++ b/src/codecs/data.cpp
@@ -0,0 +1,42 @@
+#include <ftl/codec/msgpack.hpp>
+
+using ftl::codec::pack;
+using ftl::codec::unpack;
+
+struct CameraMSGPACK : public ftl::data::Camera {
+    MSGPACK_DEFINE(fx, fy, cx, cy, width, height, minDepth, maxDepth, baseline, doffs);
+};
+
+// Instantiations supported without the msgpack.hpp header
+template void pack<int>(const int &v, std::vector<uint8_t> &out);
+template void pack<float>(const float &v, std::vector<uint8_t> &out);
+template void pack<std::string>(const std::string &v, std::vector<uint8_t> &out);
+template void pack<double>(const double &v, std::vector<uint8_t> &out);
+template void pack<std::vector<float>>(const std::vector<float> &v, std::vector<uint8_t> &out);
+template void pack<ftl::data::Pose>(const ftl::data::Pose &v, std::vector<uint8_t> &out);
+template void pack<std::vector<int>>(const std::vector<int> &v, std::vector<uint8_t> &out);
+template void pack<std::vector<std::string>>(const std::vector<std::string> &v, std::vector<uint8_t> &out);
+template void pack<ftl::data::StereoPose>(const ftl::data::StereoPose &v, std::vector<uint8_t> &out);
+template <> void ftl::codec::pack(const ftl::data::Intrinsics &v, std::vector<uint8_t> &out) {
+    std::tuple<CameraMSGPACK, int, int> data;
+    reinterpret_cast<ftl::data::Camera&>(std::get<0>(data)) = std::get<0>(v);
+    std::get<1>(data) = std::get<1>(v);
+    std::get<2>(data) = std::get<2>(v);
+    pack(data, out);
+}
+template void pack<ftl::data::Intrinsics>(const ftl::data::Intrinsics &v, std::vector<uint8_t> &out);
+
+template int unpack<int>(const std::vector<uint8_t> &in);
+template float unpack<float>(const std::vector<uint8_t> &in);
+template std::string unpack<std::string>(const std::vector<uint8_t> &in);
+template double unpack<double>(const std::vector<uint8_t> &in);
+template std::vector<float> unpack<std::vector<float>>(const std::vector<uint8_t> &in);
+template ftl::data::Pose unpack<ftl::data::Pose>(const std::vector<uint8_t> &in);
+template std::vector<int> unpack<std::vector<int>>(const std::vector<uint8_t> &in);
+template std::vector<std::string> unpack<std::vector<std::string>>(const std::vector<uint8_t> &in);
+template ftl::data::StereoPose unpack<ftl::data::StereoPose>(const std::vector<uint8_t> &in);
+template <> ftl::data::Intrinsics ftl::codec::unpack(const std::vector<uint8_t> &in) {
+    auto data = unpack<std::tuple<CameraMSGPACK, int, int>>(in);
+    return data;
+}
+template ftl::data::Intrinsics unpack<ftl::data::Intrinsics>(const std::vector<uint8_t> &in);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 5788d7966ae1d67db2ac76ba8430239e8c037ec4..e6f9fe36108dc2b4e2b2b566762e66cce4659ab1 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -151,3 +151,14 @@ target_link_libraries(rpc_integration beyond-protocol
 	${URIPARSER_LIBRARIES})
 
 add_test(RPCIntegrationTest rpc_integration)
+
+### Data Codec Unit ############################################################
+add_executable(datacodec_unit
+	$<TARGET_OBJECTS:CatchTestFTL>
+	./datacodec.cpp)
+target_include_directories(datacodec_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(datacodec_unit beyond-protocol
+	Threads::Threads ${OS_LIBS}
+	${URIPARSER_LIBRARIES})
+
+add_test(DataCodecTest datacodec_unit)
diff --git a/test/datacodec.cpp b/test/datacodec.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e94b1c4e3c50f8cd90a5556e447bf31cd0ae2939
--- /dev/null
+++ b/test/datacodec.cpp
@@ -0,0 +1,30 @@
+#include "catch.hpp"
+#include <ftl/codec/data.hpp>
+
+SCENARIO( "Intrinsics pack/unpack" ) {
+	GIVEN( "a valid instrincs object it packs" ) {
+		ftl::data::Intrinsics intrin;
+        std::get<0>(intrin).fx = 10.0f;
+
+        std::vector<uint8_t> buffer;
+        ftl::codec::pack(intrin, buffer);
+        REQUIRE(buffer.size() > 0);
+
+        auto result = ftl::codec::unpack<ftl::data::Intrinsics>(buffer);
+        REQUIRE(std::get<0>(result).fx == 10.0f);
+	}
+}
+
+SCENARIO( "Vector of strings pack/unpack" ) {
+	GIVEN( "a valid instrincs object it packs" ) {
+		std::vector<std::string> data = {"hello", "world"};
+
+        std::vector<uint8_t> buffer;
+        ftl::codec::pack(data, buffer);
+        REQUIRE(buffer.size() > 0);
+
+        auto result = ftl::codec::unpack<std::vector<std::string>>(buffer);
+        REQUIRE(result[0] == "hello");
+        REQUIRE(result[1] == "world");
+	}
+}