feat: add protocol data encrypt and decrypt module (#8)

diff --git a/Makefile b/Makefile
index 4e88c6c..8ad5cf4 100644
--- a/Makefile
+++ b/Makefile
@@ -20,3 +20,8 @@
 	python3 -m pip install a6pluginprotos --ignore-installed
 	python3 -m pip install minicache --ignore-installed
 	python3 -m pip install click --ignore-installed
+	python3 -m pip install pytest --ignore-installed
+
+.PHONY: test
+test:
+	pytest .
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..d207ff6
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,20 @@
+;
+; Licensed to the Apache Software Foundation (ASF) under one or more
+; contributor license agreements.  See the NOTICE file distributed with
+; this work for additional information regarding copyright ownership.
+; The ASF licenses this file to You under the Apache License, Version 2.0
+; (the "License"); you may not use this file except in compliance with
+; the License.  You may obtain a copy of the License at
+;
+;     http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+[pytest]
+addopts = -vs -p no:warnings
+testpaths = tests
+python_files = test_*
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..b1312a0
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,16 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
diff --git a/src/runner/socket/protocol.py b/src/runner/socket/protocol.py
new file mode 100644
index 0000000..e412981
--- /dev/null
+++ b/src/runner/socket/protocol.py
@@ -0,0 +1,86 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from src.runner.socket.response import Response as NewServerResponse
+from src.runner.socket.response import RUNNER_ERROR_CODE
+from src.runner.socket.response import RUNNER_SUCCESS_CODE
+from src.runner.socket.response import RUNNER_SUCCESS_MESSAGE
+
+
+class Protocol:
+
+    def __init__(self, buffer: bytes = b'', ty: int = 0):
+        self.__buffer = buffer
+        self.__type = ty
+        self.__length = 0
+
+    @property
+    def length(self) -> int:
+        """
+        get buffer length
+        :return:
+        """
+        return self.__length
+
+    @property
+    def type(self) -> int:
+        """
+        get protocol type
+        :return:
+        """
+        return self.__type
+
+    @property
+    def buffer(self) -> bytes:
+        """
+        get buffer data
+        :return:
+        """
+        return self.__buffer
+
+    def encode(self) -> NewServerResponse:
+        """
+        encode protocol buffer data
+        :return:
+        """
+        if len(self.__buffer) == 0:
+            return NewServerResponse(RUNNER_ERROR_CODE, "ERR: send buffer is empty")
+        response_len = len(self.__buffer)
+        response_header = response_len.to_bytes(4, byteorder="big")
+        response_header = bytearray(response_header)
+        response_header[0] = self.__type
+        response_header = bytes(response_header)
+        self.__buffer = response_header + self.__buffer
+        self.__length = len(self.__buffer)
+        return NewServerResponse(code=RUNNER_SUCCESS_CODE, message=RUNNER_SUCCESS_MESSAGE)
+
+    def decode(self) -> NewServerResponse:
+        """
+        decode protocol buffer data
+        :return:
+        """
+        if len(self.__buffer) == 0:
+            return NewServerResponse(RUNNER_ERROR_CODE, "ERR: recv buffer is empty")
+        length = len(self.__buffer)
+        if length != 4:
+            return NewServerResponse(RUNNER_ERROR_CODE,
+                                     "ERR: recv protocol type length is 4, got %d" % length)
+
+        buf = bytearray(self.__buffer)
+        self.__type = buf[0]
+        buf[0] = 0
+        self.__length = int.from_bytes(buf, byteorder="big")
+        return NewServerResponse(code=RUNNER_SUCCESS_CODE, message=RUNNER_SUCCESS_MESSAGE)
diff --git a/src/runner/socket/response.py b/src/runner/socket/response.py
new file mode 100644
index 0000000..08b0f53
--- /dev/null
+++ b/src/runner/socket/response.py
@@ -0,0 +1,59 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from __future__ import annotations
+from a6pluginproto.Err import Code as A6ErrCode
+
+RUNNER_SUCCESS_CODE = 200
+RUNNER_SUCCESS_MESSAGE = "OK"
+RUNNER_ERROR_CODE = 500
+RUNNER_ERROR_MESSAGE = "ERR"
+
+errorCodes = [
+    A6ErrCode.Code.CONF_TOKEN_NOT_FOUND,
+    A6ErrCode.Code.BAD_REQUEST,
+    A6ErrCode.Code.SERVICE_UNAVAILABLE,
+]
+
+
+class Response:
+    def __init__(self, code: int = 0, message: str = '', data: bytes = b'', ty: int = 0):
+        self.__code = code
+        self.__message = message
+        self.__type = ty
+        self.__data = data
+
+    def __eq__(self, other: Response) -> bool:
+        return self.code == other.code and \
+               self.message == other.message and \
+               self.data == other.data and \
+               self.type == other.type
+
+    @property
+    def code(self) -> int:
+        return self.__code
+
+    @property
+    def message(self) -> str:
+        return self.__message
+
+    @property
+    def data(self) -> bytes:
+        return self.__data
+
+    @property
+    def type(self) -> int:
+        return self.__type
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..8e25b19
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,4 @@
+import os
+import sys
+
+sys.path.append(os.path.dirname(os.path.dirname(__file__)))
diff --git a/tests/runner/socket/test_protocol.py b/tests/runner/socket/test_protocol.py
new file mode 100644
index 0000000..7c1a3c6
--- /dev/null
+++ b/tests/runner/socket/test_protocol.py
@@ -0,0 +1,34 @@
+from src.runner.socket.protocol import Protocol as NewServerProtocol
+from src.runner.http.protocol import RPC_PREPARE_CONF
+from src.runner.socket.response import RUNNER_SUCCESS_CODE
+from src.runner.socket.response import RUNNER_SUCCESS_MESSAGE
+
+
+def test_protocol_encode():
+    buf_str = "Hello Python Runner".encode()
+    protocol = NewServerProtocol(buffer=buf_str, ty=RPC_PREPARE_CONF)
+    err = protocol.encode()
+    buf_len = len(buf_str)
+    buf_arr = bytearray(buf_len.to_bytes(4, byteorder="big"))
+    buf_arr[0] = RPC_PREPARE_CONF
+    buf_data = bytes(buf_arr) + buf_str
+    buf_len = len(buf_data)
+    assert err.code == RUNNER_SUCCESS_CODE
+    assert err.message == RUNNER_SUCCESS_MESSAGE
+    assert protocol.type == RPC_PREPARE_CONF
+    assert protocol.buffer == buf_data
+    assert protocol.length == buf_len
+
+
+def test_protocol_decode():
+    buf_str = "Hello Python Runner".encode()
+    buf_len = len(buf_str)
+    buf_arr = bytearray(buf_len.to_bytes(4, byteorder="big"))
+    buf_arr[0] = RPC_PREPARE_CONF
+    buf_data = bytes(buf_arr)
+    protocol = NewServerProtocol(buffer=buf_data)
+    err = protocol.decode()
+    assert err.code == RUNNER_SUCCESS_CODE
+    assert err.message == RUNNER_SUCCESS_MESSAGE
+    assert protocol.type == RPC_PREPARE_CONF
+    assert protocol.length == buf_len
diff --git a/tests/runner/socket/test_response.py b/tests/runner/socket/test_response.py
new file mode 100644
index 0000000..dc9e88f
--- /dev/null
+++ b/tests/runner/socket/test_response.py
@@ -0,0 +1,43 @@
+from src.runner.socket.response import Response as NewServerResponse
+from src.runner.socket.response import RUNNER_ERROR_CODE
+from src.runner.socket.response import RUNNER_SUCCESS_CODE
+from src.runner.http.protocol import RPC_PREPARE_CONF
+from src.runner.http.protocol import RPC_HTTP_REQ_CALL
+from src.runner.http.protocol import RPC_UNKNOWN
+
+
+def test_response_code():
+    response = NewServerResponse(code=RUNNER_SUCCESS_CODE)
+    assert response.code == RUNNER_SUCCESS_CODE
+    error = NewServerResponse(code=RUNNER_ERROR_CODE)
+    assert error.code == RUNNER_ERROR_CODE
+
+
+def test_response_message():
+    response = NewServerResponse(message="Hello Python Runner")
+    assert response.message == "Hello Python Runner"
+
+
+def test_response_data():
+    response = NewServerResponse(data="Hello Python Runner".encode())
+    assert response.data == b'Hello Python Runner'
+
+
+def test_response_type():
+    response = NewServerResponse(ty=RPC_UNKNOWN)
+    assert response.type == RPC_UNKNOWN
+    response = NewServerResponse(ty=RPC_PREPARE_CONF)
+    assert response.type == RPC_PREPARE_CONF
+    response = NewServerResponse(ty=RPC_HTTP_REQ_CALL)
+    assert response.type == RPC_HTTP_REQ_CALL
+
+
+def test_response_eq():
+    resp1 = NewServerResponse(code=RUNNER_SUCCESS_CODE, message="Hello Python Runner",
+                              data="Hello Python Runner".encode(), ty=RPC_PREPARE_CONF)
+    resp2 = NewServerResponse(code=RUNNER_ERROR_CODE, message="Hello Python Runner",
+                              data="Hello Python Runner".encode(), ty=RPC_PREPARE_CONF)
+    resp3 = NewServerResponse(code=RUNNER_SUCCESS_CODE, message="Hello Python Runner",
+                              data="Hello Python Runner".encode(), ty=RPC_PREPARE_CONF)
+    assert resp1 != resp2
+    assert resp1 == resp3