summaryrefslogtreecommitdiffstats
path: root/include/network
diff options
context:
space:
mode:
authorLaG1924 <12997935+LaG1924@users.noreply.github.com>2017-06-21 16:26:25 +0200
committerLaG1924 <12997935+LaG1924@users.noreply.github.com>2017-06-21 16:26:25 +0200
commitf5aa7827af55342a1ec714d366944b3e3f3129b2 (patch)
tree4f4885d2ae0f46004a22e7d4dc34f80c11ecee0c /include/network
parent2017-06-20 (diff)
downloadAltCraft-f5aa7827af55342a1ec714d366944b3e3f3129b2.tar
AltCraft-f5aa7827af55342a1ec714d366944b3e3f3129b2.tar.gz
AltCraft-f5aa7827af55342a1ec714d366944b3e3f3129b2.tar.bz2
AltCraft-f5aa7827af55342a1ec714d366944b3e3f3129b2.tar.lz
AltCraft-f5aa7827af55342a1ec714d366944b3e3f3129b2.tar.xz
AltCraft-f5aa7827af55342a1ec714d366944b3e3f3129b2.tar.zst
AltCraft-f5aa7827af55342a1ec714d366944b3e3f3129b2.zip
Diffstat (limited to 'include/network')
-rw-r--r--include/network/Network.hpp26
-rw-r--r--include/network/NetworkClient.hpp26
-rw-r--r--include/network/Packet.hpp521
-rw-r--r--include/network/Socket.hpp46
-rw-r--r--include/network/Stream.hpp107
5 files changed, 726 insertions, 0 deletions
diff --git a/include/network/Network.hpp b/include/network/Network.hpp
new file mode 100644
index 0000000..1281289
--- /dev/null
+++ b/include/network/Network.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <memory>
+#include "Socket.hpp"
+#include "Packet.hpp"
+
+enum ConnectionState {
+ Handshaking,
+ Login,
+ Play,
+ Status,
+};
+
+class Network {
+ Socket *socket;
+ StreamSocket *stream;
+
+ std::shared_ptr<Packet> ReceivePacketByPacketId(int packetId, ConnectionState state, StreamInput &stream);
+public:
+ Network(std::string address, unsigned short port);
+ ~Network();
+
+ std::shared_ptr<Packet> ReceivePacket(ConnectionState state = Play);
+ void SendPacket(Packet &packet);
+ std::shared_ptr<Packet> ParsePacketPlay(PacketNamePlayCB id);
+}; \ No newline at end of file
diff --git a/include/network/NetworkClient.hpp b/include/network/NetworkClient.hpp
new file mode 100644
index 0000000..22b1b22
--- /dev/null
+++ b/include/network/NetworkClient.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <thread>
+#include <queue>
+#include <mutex>
+
+#include <network/Network.hpp>
+
+class NetworkClient {
+ Network network;
+ std::thread networkThread;
+ std::mutex toSendMutex;
+ std::mutex toReceiveMutex;
+ std::queue <std::shared_ptr<Packet>> toSend;
+ std::queue <std::shared_ptr<Packet>> toReceive;
+ bool isActive=true;
+ bool &isRunning;
+ ConnectionState state;
+ void NetworkLoop();
+public:
+ NetworkClient(std::string address, unsigned short port, std::string username, bool &quit);
+ ~NetworkClient();
+
+ std::shared_ptr <Packet> ReceivePacket();
+ void SendPacket(std::shared_ptr<Packet> packet);
+}; \ No newline at end of file
diff --git a/include/network/Packet.hpp b/include/network/Packet.hpp
new file mode 100644
index 0000000..685e3da
--- /dev/null
+++ b/include/network/Packet.hpp
@@ -0,0 +1,521 @@
+#pragma once
+
+#include <easylogging++.h>
+
+#include <network/Stream.hpp>
+
+enum PacketNameLoginSB {
+ LoginStart = 0x00,
+ EncryptionResponse = 0x01,
+};
+enum PacketNamePlaySB {
+ TeleportConfirm,
+ PrepareCraftingGrid,
+ TabCompleteSB,
+ ChatMessageSB,
+ ClientStatus,
+ ClientSettings,
+ ConfirmTransactionSB,
+ EnchantItem,
+ ClickWindow,
+ CloseWindowSB,
+ PluginMessageSB,
+ UseEntity,
+ KeepAliveSB,
+ Player,
+ PlayerPosition,
+ PlayerPositionAndLookSB,
+ PlayerLook,
+ VehicleMoveSB,
+ SteerBoat,
+ PlayerAbilitiesSB,
+ PlayerDigging,
+ EntityAction,
+ SteerVehicle,
+ CraftingBookData,
+ ResourcePackStatus,
+ AdvancementTab,
+ HeldItemChangeSB,
+ CreativeInventoryAction,
+ UpdateSign,
+ AnimationSB,
+ Spectate,
+ PlayerBlockPlacement,
+ UseItem,
+};
+
+enum PacketNameHandshakingCB {
+ Handshake = 0x00,
+};
+enum PacketNameLoginCB {
+ Disconnect = 0x00,
+ EncryptionRequest = 0x01,
+ LoginSuccess = 0x02,
+ SetCompression = 0x03,
+};
+enum PacketNamePlayCB {
+ SpawnObject = 0x00,
+ SpawnExperienceOrb,
+ SpawnGlobalEntity,
+ SpawnMob,
+ SpawnPainting,
+ SpawnPlayer,
+ AnimationCB,
+ Statistics,
+ BlockBreakAnimation,
+ UpdateBlockEntity,
+ BlockAction,
+ BlockChange,
+ BossBar,
+ ServerDifficulty,
+ TabCompleteCB,
+ ChatMessageCB,
+ MultiBlockChange,
+ ConfirmTransactionCB,
+ CloseWindowCB,
+ OpenWindow,
+ WindowItems,
+ WindowProperty,
+ SetSlot,
+ SetCooldown,
+ PluginMessageCB,
+ NamedSoundEffect,
+ DisconnectPlay,
+ EntityStatus,
+ Explosion,
+ UnloadChunk,
+ ChangeGameState,
+ KeepAliveCB,
+ ChunkData,
+ Effect,
+ Particle,
+ JoinGame,
+ Map,
+ EntityRelativeMove,
+ EntityLookAndRelativeMove,
+ EntityLook,
+ Entity,
+ VehicleMove,
+ OpenSignEditor,
+ PlayerAbilitiesCB,
+ CombatEvent,
+ PlayerListItem,
+ PlayerPositionAndLookCB,
+ UseBed,
+ UnlockRecipes,
+ DestroyEntities,
+ RemoveEntityEffect,
+ ResourcePackSend,
+ Respawn,
+ EntityHeadLook,
+ SelectAdvancementTab,
+ WorldBorder,
+ Camera,
+ HeldItemChangeCB,
+ DisplayScoreboard,
+ EntityMetadata,
+ AttachEntity,
+ EntityVelocity,
+ EntityEquipment,
+ SetExperience,
+ UpdateHealth,
+ ScoreboardObjective,
+ SetPassengers,
+ Teams,
+ UpdateScore,
+ SpawnPosition,
+ TimeUpdate,
+ Title,
+ SoundEffect,
+ PlayerListHeaderAndFooter,
+ CollectItem,
+ EntityTeleport,
+ Advancements,
+ EntityProperties,
+ EntityEffect,
+};
+
+struct Packet {
+ virtual ~Packet() = default;
+ virtual void ToStream(StreamOutput *stream) = 0;
+ virtual void FromStream(StreamInput *stream) = 0;
+ virtual int GetPacketId() = 0;
+};
+
+struct PacketHandshake : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteVarInt(protocolVersion);
+ stream->WriteString(serverAddress);
+ stream->WriteUShort(serverPort);
+ stream->WriteVarInt(nextState);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ protocolVersion = stream->ReadVarInt();
+ serverAddress = stream->ReadString();
+ serverPort = stream->ReadUShort();
+ nextState = stream->ReadVarInt();
+ }
+
+ int GetPacketId() override {
+ return PacketNameHandshakingCB::Handshake;
+ }
+
+ int protocolVersion;
+ std::string serverAddress;
+ unsigned short serverPort;
+ int nextState;
+};
+
+struct PacketLoginStart : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteString(Username);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ Username = stream->ReadString();
+ }
+
+ int GetPacketId() override {
+ return PacketNameLoginSB::LoginStart;
+ }
+
+ std::string Username;
+};
+
+struct PacketLoginSuccess : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteString(Uuid);
+ stream->WriteString(Username);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ Uuid = stream->ReadString();
+ Username = stream->ReadString();
+ }
+
+ int GetPacketId() override {
+ return PacketNameLoginCB::LoginSuccess;
+ }
+
+ std::string Uuid;
+ std::string Username;
+};
+
+struct PacketJoinGame : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteInt(EntityId);
+ stream->WriteUByte(Gamemode);
+ stream->WriteInt(Dimension);
+ stream->WriteUByte(Difficulty);
+ stream->WriteUByte(MaxPlayers);
+ stream->WriteString(LevelType);
+ stream->WriteBool(ReducedDebugInfo);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ EntityId = stream->ReadInt();
+ Gamemode = stream->ReadUByte();
+ Dimension = stream->ReadInt();
+ Difficulty = stream->ReadUByte();
+ MaxPlayers = stream->ReadUByte();
+ LevelType = stream->ReadString();
+ ReducedDebugInfo = stream->ReadBool();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlayCB::JoinGame;
+ }
+
+ int EntityId;
+ unsigned char Gamemode;
+ int Dimension;
+ unsigned char Difficulty;
+ unsigned char MaxPlayers;
+ std::string LevelType;
+ bool ReducedDebugInfo;
+};
+
+struct PacketDisconnectPlay : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteString(Reason); //TODO: Implement chat-wrapper
+ }
+
+ void FromStream(StreamInput *stream) override {
+ Reason = stream->ReadChat();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlayCB::DisconnectPlay;
+ }
+
+ std::string Reason;
+};
+
+struct PacketSpawnPosition : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WritePosition(Location);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ Location = stream->ReadPosition();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlayCB::SpawnPosition;
+ }
+
+ Vector Location;
+};
+
+struct PacketKeepAliveCB : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteVarInt(KeepAliveId);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ KeepAliveId = stream->ReadVarInt();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlayCB::KeepAliveCB;
+ }
+
+ int KeepAliveId;
+};
+
+struct PacketKeepAliveSB : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteVarInt(KeepAliveId);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ KeepAliveId = stream->ReadVarInt();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlaySB::KeepAliveSB;
+ }
+
+ int KeepAliveId;
+
+ PacketKeepAliveSB(int KeepAliveId) : KeepAliveId(KeepAliveId) {}
+};
+
+struct PacketPlayerPositionAndLookCB : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteDouble(X);
+ stream->WriteDouble(Y);
+ stream->WriteDouble(Z);
+ stream->WriteFloat(Yaw);
+ stream->WriteFloat(Pitch);
+ stream->WriteUByte(Flags);
+ stream->WriteVarInt(TeleportId);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ X = stream->ReadDouble();
+ Y = stream->ReadDouble();
+ Z = stream->ReadDouble();
+ Yaw = stream->ReadFloat();
+ Pitch = stream->ReadFloat();
+ Flags = stream->ReadUByte();
+ TeleportId = stream->ReadVarInt();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlayCB::PlayerPositionAndLookCB;
+ }
+
+ double X;
+ double Y;
+ double Z;
+ float Yaw;
+ float Pitch;
+ unsigned char Flags;
+ int TeleportId;
+};
+
+struct PacketTeleportConfirm : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteVarInt(TeleportId);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ TeleportId = stream->ReadVarInt();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlaySB::TeleportConfirm;
+ }
+
+ int TeleportId;
+
+ PacketTeleportConfirm(int TeleportId) : TeleportId(TeleportId) {}
+};
+
+struct PacketClientStatus : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteVarInt(ActionId);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ ActionId = stream->ReadVarInt();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlaySB::ClientStatus;
+ }
+
+ int ActionId;
+
+ PacketClientStatus(int ActionId) : ActionId(ActionId) {}
+};
+
+struct PacketPlayerPositionAndLookSB : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteDouble(X);
+ stream->WriteDouble(FeetY);
+ stream->WriteDouble(Z);
+ stream->WriteFloat(Yaw);
+ stream->WriteFloat(Pitch);
+ stream->WriteBool(OnGround);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ X = stream->ReadDouble();
+ FeetY = stream->ReadDouble();
+ Z = stream->ReadDouble();
+ Yaw = stream->ReadFloat();
+ Pitch = stream->ReadFloat();
+ OnGround = stream->ReadBool();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlaySB::PlayerPositionAndLookSB;
+ }
+
+
+ double X;
+ double FeetY;
+ double Z;
+ float Yaw;
+ float Pitch;
+ bool OnGround;
+
+ PacketPlayerPositionAndLookSB(double X, double FeetY, double Z,
+ float Yaw, float Pitch, bool OnGround) : X(X), FeetY(FeetY), Z(Z), Yaw(Yaw),
+ Pitch(Pitch), OnGround(OnGround) {}
+};
+
+struct PacketChunkData : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteInt(ChunkX);
+ stream->WriteInt(ChunkZ);
+ stream->WriteBool(GroundUpContinuous);
+ stream->WriteInt(PrimaryBitMask);
+ stream->WriteVarInt(Data.size());
+ stream->WriteByteArray(Data);
+ stream->WriteVarInt(BlockEntities.size());
+ LOG(FATAL) << "Serializing unimplemented packet";
+ }
+
+ void FromStream(StreamInput *stream) override {
+ ChunkX = stream->ReadInt();
+ ChunkZ = stream->ReadInt();
+ GroundUpContinuous = stream->ReadBool();
+ PrimaryBitMask = stream->ReadVarInt();
+ int Size = stream->ReadVarInt();
+ Data = stream->ReadByteArray(Size);
+ int NumberOfBlockEntities = stream->ReadVarInt(); //TODO: Need NBT
+ for (int i = 0; i < NumberOfBlockEntities; i++) {
+ //BlockEntities[i] = stream->ReadNbt();
+ }
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlayCB::ChunkData;
+ }
+
+ int ChunkX;
+ int ChunkZ;
+ bool GroundUpContinuous;
+ int PrimaryBitMask;
+ //int Size;
+ std::vector<unsigned char> Data;
+ //int NumberOfBlockEntities;
+ std::vector<int> BlockEntities; //TODO: Replace int with NbtTag and implement NbtTree
+};
+
+struct PacketPlayerPosition : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteDouble(X);
+ stream->WriteDouble(FeetY);
+ stream->WriteDouble(Z);
+ stream->WriteBool(OnGround);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ X = stream->ReadDouble();
+ FeetY = stream->ReadDouble();
+ Z = stream->ReadDouble();
+ OnGround = stream->ReadBool();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlaySB::PlayerPosition;
+ }
+
+ double X;
+ double FeetY;
+ double Z;
+ bool OnGround;
+
+ PacketPlayerPosition(double X, double Y, double Z, bool ground) : X(X), FeetY(Y), Z(Z), OnGround(ground) {}
+};
+
+struct PacketPlayerLook : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteFloat(Yaw);
+ stream->WriteFloat(Pitch);
+ stream->WriteBool(OnGround);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ Yaw = stream->ReadFloat();
+ Pitch = stream->ReadFloat();
+ OnGround = stream->ReadBool();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlaySB::PlayerLook;
+ }
+
+ float Yaw;
+ float Pitch;
+ bool OnGround;
+
+ PacketPlayerLook(float Yaw, float Pitch, bool ground) : Yaw(Yaw), Pitch(Pitch), OnGround(ground) {}
+};
+
+struct PacketUpdateHealth : Packet {
+ void ToStream(StreamOutput *stream) override {
+ stream->WriteFloat(Health);
+ stream->WriteVarInt(Food);
+ stream->WriteFloat(FoodSaturation);
+ }
+
+ void FromStream(StreamInput *stream) override {
+ Health = stream->ReadFloat();
+ Food = stream->ReadVarInt();
+ FoodSaturation = stream->ReadFloat();
+ }
+
+ int GetPacketId() override {
+ return PacketNamePlayCB::UpdateHealth;
+ }
+
+ float Health;
+ int Food;
+ float FoodSaturation;
+}; \ No newline at end of file
diff --git a/include/network/Socket.hpp b/include/network/Socket.hpp
new file mode 100644
index 0000000..48bcad9
--- /dev/null
+++ b/include/network/Socket.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <string>
+
+#include <SFML/Network.hpp>
+
+/**
+ * Platform independent class for working with platform dependent hardware socket
+ * @brief Wrapper around raw sockets
+ * @warning Connection state is based on lifetime of Socket object instance, ie connected at ctor and disconnect at dtor
+ * @todo Replace SFML's socket with WinSock and POSIX's socket implementation
+ */
+class Socket {
+ sf::TcpSocket socket;
+public:
+ /**
+ * Constructs Socket class instance from IP's string and Port number and connects to remote server
+ * @param[in] address IP address of remote server. String should be ANSI and contains 4 one-byte values separated by dots
+ * @param[in] port target port of remote server to connect
+ * @throw std::runtime_error if connection is failed
+ */
+ Socket(std::string address, unsigned short port);
+
+ /**
+ * Destruct Socket instance and disconnect from server
+ * @warning There is no way to force disconnect, except use delete for manually allocated objects and scope of visibility for variables on stack
+ */
+ ~Socket();
+
+ /**
+ * Reads data from socket and write to buffer
+ * @warning This is blocking function, and execution flow will not be returned until all required data is sended
+ * @warning Reported buffer length must be <= actual size of buffer, or memory corruption will be caused
+ * @param[out] buffPtr Pointer to buffer, where data must be placed
+ * @param[in] buffLen Length of data, that must be readed from server and writed to buffer
+ */
+ void Read(unsigned char *buffPtr, size_t buffLen);
+
+ /**
+ * Writes data from buffer to socket
+ * @warning This is blocking function, and execution flow will not be returned until all required data is received
+ * @param[in] buffPtr Pointer to buffer that contain data to send
+ * @param[in] buffLen Length of buffer
+ */
+ void Write(unsigned char *buffPtr, size_t buffLen);
+}; \ No newline at end of file
diff --git a/include/network/Stream.hpp b/include/network/Stream.hpp
new file mode 100644
index 0000000..a24dfbe
--- /dev/null
+++ b/include/network/Stream.hpp
@@ -0,0 +1,107 @@
+#pragma once
+
+#include <algorithm>
+#include <string>
+#include <stdexcept>
+#include <vector>
+#include <cstring>
+
+#include <nlohmann/json.hpp>
+#include <easylogging++.h>
+
+#include <network/Socket.hpp>
+#include <Vector.hpp>
+#include <Utility.hpp>
+
+class Stream {
+public:
+ virtual ~Stream() {};
+};
+
+class StreamInput : Stream {
+ virtual void ReadData(unsigned char *buffPtr, size_t buffLen) = 0;
+public:
+ virtual ~StreamInput() = default;
+ bool ReadBool();
+ signed char ReadByte();
+ unsigned char ReadUByte();
+ short ReadShort();
+ unsigned short ReadUShort();
+ int ReadInt();
+ long long ReadLong();
+ float ReadFloat();
+ double ReadDouble();
+ std::string ReadString();
+ std::string ReadChat();
+ int ReadVarInt();
+ long long ReadVarLong();
+ std::vector<unsigned char> ReadEntityMetadata();
+ std::vector<unsigned char> ReadSlot();
+ std::vector<unsigned char> ReadNbtTag();
+ Vector ReadPosition();
+ unsigned char ReadAngle();
+ std::vector<unsigned char> ReadUuid();
+ std::vector<unsigned char> ReadByteArray(size_t arrLength);
+};
+
+class StreamOutput : Stream {
+ virtual void WriteData(unsigned char *buffPtr, size_t buffLen) = 0;
+public:
+ virtual ~StreamOutput() = default;
+ void WriteBool(bool value);
+ void WriteByte(signed char value);
+ void WriteUByte(unsigned char value);
+ void WriteShort(short value);
+ void WriteUShort(unsigned short value);
+ void WriteInt(int value);
+ void WriteLong(long long value);
+ void WriteFloat(float value);
+ void WriteDouble(double value);
+ void WriteString(std::string value);
+ void WriteChat(std::string value);
+ void WriteVarInt(int value);
+ void WriteVarLong(long long value);
+ void WriteEntityMetadata(std::vector<unsigned char> value);
+ void WriteSlot(std::vector<unsigned char> value);
+ void WriteNbtTag(std::vector<unsigned char> value);
+ void WritePosition(Vector value);
+ void WriteAngle(unsigned char value);
+ void WriteUuid(std::vector<unsigned char> value);
+ void WriteByteArray(std::vector<unsigned char> value);
+};
+
+class StreamBuffer : public StreamInput, public StreamOutput {
+ unsigned char *buffer;
+ unsigned char *bufferPtr;
+ size_t bufferLength;
+
+ void ReadData(unsigned char *buffPtr, size_t buffLen) override;
+ void WriteData(unsigned char *buffPtr, size_t buffLen) override;
+
+public:
+ StreamBuffer(unsigned char *data, size_t dataLen);
+ StreamBuffer(size_t bufferLen);
+ ~StreamBuffer();
+
+ std::vector<unsigned char> GetBuffer();
+};
+
+class StreamCounter : public StreamOutput {
+ void WriteData(unsigned char *buffPtr, size_t buffLen) override;
+
+ size_t size;
+public:
+ StreamCounter(size_t initialSize = 0);
+ ~StreamCounter();
+
+ size_t GetCountedSize();
+};
+
+class StreamSocket : public StreamInput, public StreamOutput {
+ Socket *socket;
+ void ReadData(unsigned char *buffPtr, size_t buffLen) override;
+ void WriteData(unsigned char *buffPtr, size_t buffLen) override;
+public:
+ StreamSocket(Socket *socketPtr);
+ ~StreamSocket() = default;
+}; \ No newline at end of file