├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dub.json ├── examples ├── buffer_example │ ├── .gitignore │ ├── dub.json │ └── src │ │ └── app.d ├── message.buffer ├── rpc_client │ ├── .gitignore │ ├── dub.json │ └── src │ │ └── app.d └── rpc_server │ ├── .gitignore │ ├── dub.json │ └── src │ └── app.d ├── libbuffer └── src │ └── buffer │ ├── compiler.d │ ├── message.d │ ├── package.d │ ├── packet.d │ ├── rpc │ ├── client.d │ └── server.d │ └── utils.d └── other_client └── C ├── README.md └── compiler ├── .gitignore ├── dub.json └── src └── app.d /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | **/.dub/ 3 | **/dub.selections.json 4 | /libbuffer.a 5 | /buffer.lib 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | sudo: false 3 | os: 4 | - linux 5 | - osx 6 | d: 7 | - dmd 8 | - ldc 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 shove70(shove) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/shove70/buffer.svg?branch=master)](https://travis-ci.org/shove70/buffer) 2 | [![GitHub tag](https://img.shields.io/github/tag/shove70/buffer.svg?maxAge=86400)](https://github.com/shove70/buffer/releases) 3 | [![Dub downloads](https://img.shields.io/dub/dt/buffer.svg)](http://code.dlang.org/packages/buffer) 4 | 5 | # A simple and practical protocol buffer & RPC library. 6 | 7 | At present, it has only dlang and C++ implementations, and will add more language support in the future, such as: Lua, Java, python, ruby, golang, rust... 8 | 9 | C++ project on github: 10 | 11 | https://github.com/shove70/shove.c 12 | 13 | 14 | ### Quick Start: 15 | 16 | ```d 17 | import std.stdio; 18 | import buffer.message; 19 | 20 | mixin (LoadBufferFile!"message.buffer"); 21 | mixin (LoadBufferScript!` 22 | message Sample { 23 | string name; 24 | int32 age; 25 | int16 sex; 26 | } 27 | `); 28 | 29 | // Simple: 30 | 31 | void main() 32 | { 33 | Sample sample = new Sample(); 34 | sample.name = "Tom"; 35 | sample.age = 20; 36 | sample.sex = 1; 37 | ubyte[] buf = sample.serialize(); 38 | writeln(buf); 39 | 40 | Sample sam = Message.deserialize!Sample(buf); 41 | writeln("name:\t", sam.name); 42 | writeln("age:\t", sam.age); 43 | writeln("sex:\t", sam.sex); 44 | } 45 | 46 | // Advanced: 47 | 48 | void main() 49 | { 50 | // Set magic number, encryption method and key. 51 | Message.settings(1229, CryptType.XTEA, "1234"); 52 | 53 | Sample sample = new Sample(); 54 | sample.name = "Tom"; 55 | sample.age = 20; 56 | sample.sex = 1; 57 | ubyte[] buf = sample.serialize(); 58 | writeln(buf); 59 | 60 | string name; 61 | string method; 62 | Message.getMessageInfo(buf, name, method); 63 | 64 | switch (name) 65 | { 66 | case "Sample": 67 | Sample sam = Message.deserialize!Sample(buf); 68 | writeln("name:\t", sam.name); 69 | writeln("age:\t", sam.age); 70 | writeln("sex:\t", sam.sex); 71 | break; 72 | case "...": 73 | break; 74 | default: 75 | break; 76 | } 77 | } 78 | ``` 79 | 80 | ### RPC Client: 81 | 82 | ```d 83 | mixin(LoadBufferScript!` 84 | message LoginInfo { 85 | string name; 86 | string password; 87 | } 88 | 89 | message LoginRetInfo { 90 | int32 id; 91 | string name; 92 | } 93 | `); 94 | 95 | void main() 96 | { 97 | Message.settings(1229, CryptType.XTEA, "1234"); 98 | Client.setServerHost("127.0.0.1", 10_000); 99 | 100 | LoginRetInfo ret = Client.call!LoginRetInfo("login", "admin", "123456"); 101 | if (ret !is null) 102 | { 103 | writeln(ret.id); 104 | writeln(ret.name); 105 | } 106 | 107 | // or: 108 | 109 | long userId = Client.call!long("getUserId", "admin"); 110 | writeln(userId); 111 | } 112 | ``` 113 | 114 | ### RPC Server: 115 | 116 | ```d 117 | class Business 118 | { 119 | mixin(LoadBufferScript!` 120 | message LoginInfo { 121 | string name; 122 | string password; 123 | } 124 | 125 | message LoginRetInfo { 126 | int32 id; 127 | string name; 128 | } 129 | `); 130 | 131 | long GetUserId(string name) 132 | { 133 | // Access the database, query the user's id by name, assuming the user's ID is 1 134 | int userId = 1; 135 | // ... 136 | // Query OK. 137 | 138 | return userId; 139 | } 140 | 141 | LoginRetInfo Login(string name, string password) 142 | { 143 | // Access the database, check the user name and password, assuming the validation passed, the user's ID is 1 144 | int userId = 1; 145 | // ... 146 | // Check OK. 147 | 148 | LoginRetInfo ret = new LoginRetInfo(); 149 | ret.id = userId; 150 | ret.name = name; 151 | 152 | return ret; 153 | } 154 | } 155 | 156 | __gshared Server!(Business) server; 157 | 158 | void main() 159 | { 160 | Message.settings(1229, CryptType.XTEA, "1234"); 161 | server = new Server!(Business)(); 162 | 163 | TcpSocket socket = new TcpSocket(); 164 | socket.bind(new InternetAddress("127.0.0.1", 10000)); 165 | socket.listen(10); 166 | 167 | while (true) 168 | { 169 | Socket accept = socket.accept(); 170 | spawn(&acceptHandler, cast(shared Socket) accept); 171 | } 172 | } 173 | 174 | void acceptHandler(shared Socket accept) 175 | { 176 | Socket socket = cast(Socket) accept; 177 | 178 | while (true) 179 | { 180 | ubyte[] data = new ubyte[1024]; 181 | long len = socket.receive(data); 182 | 183 | if (len > 0) 184 | { 185 | ubyte[] ret_data = server.Handler(data[0..len]); 186 | if (ret_data != null) 187 | socket.send(ret_data); 188 | } 189 | } 190 | } 191 | ``` 192 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": ["Shove"], 3 | "name": "buffer", 4 | "description": "A protocol buffer & RPC library", 5 | "license": "MIT", 6 | "copyright": "Copyright © 2017-2020, Shove", 7 | "targetType": "library", 8 | "targetName": "buffer", 9 | "importPaths": [ 10 | "libbuffer/src" 11 | ], 12 | "sourcePaths": [ 13 | "libbuffer/src" 14 | ], 15 | "dependencies": { 16 | "crypto": "*" 17 | } 18 | } -------------------------------------------------------------------------------- /examples/buffer_example/.gitignore: -------------------------------------------------------------------------------- 1 | /buffer_example 2 | /buffer_example.exe 3 | -------------------------------------------------------------------------------- /examples/buffer_example/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Shove" 4 | ], 5 | "name": "buffer_example", 6 | "targetType": "executable", 7 | "license": "MIT", 8 | "copyright": "Copyright © 2017-2020, Shove", 9 | "targetName": "buffer_example", 10 | "description": "a example for buffer.", 11 | "dependencies": { 12 | "buffer": { 13 | "path": "../../" 14 | } 15 | }, 16 | "stringImportPaths": [ 17 | "../" 18 | ] 19 | } -------------------------------------------------------------------------------- /examples/buffer_example/src/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | import buffer.message; 4 | 5 | mixin(LoadBufferFile!"message.buffer"); 6 | 7 | mixin(LoadBufferScript!` 8 | message Sample { 9 | string name; 10 | int32 age; 11 | int16 sex; 12 | } 13 | `); 14 | 15 | // Simple: 16 | void main() 17 | { 18 | Sample sample = new Sample(); 19 | sample.name = "Tom"; 20 | sample.age = 20; 21 | sample.sex = 1; 22 | ubyte[] buf = sample.serialize(); 23 | writeln(buf); 24 | 25 | Sample sam = Message.deserialize!Sample(buf); 26 | writeln("name:\t", sam.name); 27 | writeln("age:\t", sam.age); 28 | writeln("sex:\t", sam.sex); 29 | } 30 | 31 | // Advanced: 32 | void main_() 33 | { 34 | string publicKey = "AAAAIEsD3P0HLddeKShoVDfNCdOl6krCWBS/FPTyWCf15tOZ/2U="; 35 | string privateKey = "AAAAIEsD3P0HLddeKShoVDfNCdOl6krCWBS/FPTyWCf15tOZOD1j37Rl0gAyVRNy7AVBbFrdERVgxJE1OxHm6AGajXE="; 36 | 37 | // Set magic number, encryption method and key. 38 | Message.settings(1229, CryptType.RSA_XTEA_MIXIN, publicKey); 39 | 40 | Sample sample = new Sample(); 41 | sample.name = "Tom"; 42 | sample.age = 20; 43 | sample.sex = 1; 44 | ubyte[] buf = sample.serialize(); 45 | writeln(buf); 46 | 47 | Message.settings(1229, CryptType.RSA_XTEA_MIXIN, privateKey); 48 | string name; 49 | string method; 50 | Message.getMessageInfo(buf, name, method); 51 | 52 | switch (name) 53 | { 54 | case "Sample": 55 | Sample sam = Message.deserialize!Sample(buf); 56 | writeln("name:\t", sam.name); 57 | writeln("age:\t", sam.age); 58 | writeln("sex:\t", sam.sex); 59 | break; 60 | case "...": 61 | break; 62 | default: 63 | break; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/message.buffer: -------------------------------------------------------------------------------- 1 | message RegisterInfo { 2 | string name; 3 | string password; 4 | int8 age; 5 | string phone; 6 | string email; 7 | } 8 | 9 | message LoginInfo { 10 | string name; 11 | string password; 12 | int32 hallNo; 13 | } 14 | 15 | /* 16 | int8, dlang: byte c++: char 0x01 17 | uint8, dlang: ubyte c++: ubyte 0x02 (unsigned char) 18 | int16, dlang: short c++: short 0x03 19 | uint16, dlang: ushort c++: ushort 0x04 (unsigned short) 20 | int32, dlang: int c++: int 0x05 21 | uint32, dlang: uint c++: uint 0x06 (unsigned int) 22 | int64, dlang: long c++: int64 0x07 (long or long long) 23 | uint64, dlang: ulong c++: uint64 0x08 (unsigned long or long long) 24 | float32, dlang: float c++: float 0x20 25 | float64, dlang: double c++: double 0x21 26 | float128, dlang: real c++: float128 0x22 (long double) 27 | bool, dlang: bool c++: bool 0x30 28 | char, dlang: char c++: ubyte 0x40 (unsigned char) 29 | string, dlang: string c++: string 0x41 (or char*, const char*) 30 | 31 | Magic, length, nameLength, name, methodLength, method, TLV(serialize), CRC 32 | 2Bytes,int32(4), 2Bytes, string(size = N), 2Bytes, string(size = N), size = N , 2Bytes 33 | 34 | TLV: 35 | int8, int16, ..., string 36 | type+data, type+data, ..., type+len+data 37 | 38 | type: ubyte 39 | len: int32(4) 40 | */ -------------------------------------------------------------------------------- /examples/rpc_client/.gitignore: -------------------------------------------------------------------------------- 1 | /rpc_client 2 | /rpc_client.exe 3 | 4 | /.dub/ -------------------------------------------------------------------------------- /examples/rpc_client/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Shove" 4 | ], 5 | "name": "rpc_client", 6 | "targetType": "executable", 7 | "license": "MIT", 8 | "copyright": "Copyright © 2017-2020, Shove", 9 | "targetName": "rpc_client", 10 | "description": "a example for rpc client.", 11 | "dependencies": { 12 | "buffer": { 13 | "path": "../../" 14 | } 15 | }, 16 | "stringImportPaths": [ 17 | "../" 18 | ] 19 | } -------------------------------------------------------------------------------- /examples/rpc_client/src/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.socket; 3 | 4 | import buffer; 5 | import buffer.rpc.client; 6 | 7 | mixin(LoadBufferScript!` 8 | message LoginInfo { 9 | string name; 10 | string password; 11 | } 12 | 13 | message LoginRetInfo { 14 | int32 id; 15 | string name; 16 | } 17 | `); 18 | 19 | void main() 20 | { 21 | Message.settings(1229, CryptType.XTEA, "1234"); 22 | Client.setServerHost("127.0.0.1", 10_000); 23 | 24 | LoginRetInfo ret = Client.call!LoginRetInfo("login", "admin", "123456"); 25 | if (ret !is null) 26 | { 27 | writeln(ret.id); 28 | writeln(ret.name); 29 | } 30 | 31 | // or: 32 | 33 | long userId = Client.call!long("getUserId", "admin"); 34 | writeln(userId); 35 | } 36 | -------------------------------------------------------------------------------- /examples/rpc_server/.gitignore: -------------------------------------------------------------------------------- 1 | /rpc_server 2 | /rpc_server.exe 3 | 4 | /.dub/ -------------------------------------------------------------------------------- /examples/rpc_server/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Shove" 4 | ], 5 | "name": "rpc_server", 6 | "targetType": "executable", 7 | "license": "MIT", 8 | "copyright": "Copyright © 2017-2020, Shove", 9 | "targetName": "rpc_server", 10 | "description": "a example for rpc server.", 11 | "dependencies": { 12 | "buffer": { 13 | "path": "../../" 14 | } 15 | }, 16 | "stringImportPaths": [ 17 | "../" 18 | ] 19 | } -------------------------------------------------------------------------------- /examples/rpc_server/src/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.socket; 3 | import std.concurrency; 4 | import core.thread; 5 | 6 | import buffer; 7 | import buffer.rpc.server; 8 | 9 | class Business 10 | { 11 | mixin(LoadBufferScript!` 12 | message LoginInfo { 13 | string name; 14 | string password; 15 | } 16 | 17 | message LoginRetInfo { 18 | int32 id; 19 | string name; 20 | } 21 | `); 22 | 23 | LoginRetInfo login(string name, string password) 24 | { 25 | // Access the database, check the user name and password, assuming the validation passed, the user's ID is 1 26 | int userId = 1; 27 | // ... 28 | // Check OK. 29 | 30 | LoginRetInfo ret = new LoginRetInfo(); 31 | ret.id = userId; 32 | ret.name = name; 33 | 34 | return ret; 35 | } 36 | 37 | long getUserId(string name) 38 | { 39 | // Access the database, query the user's id by name, assuming the user's ID is 1 40 | long userId = 1; 41 | // ... 42 | // Query OK. 43 | 44 | return userId; 45 | } 46 | } 47 | 48 | 49 | __gshared Server!(Business) server; 50 | 51 | void main() 52 | { 53 | Message.settings(1229, CryptType.XTEA, "1234"); 54 | server = new Server!(Business)(); 55 | 56 | TcpSocket socket = new TcpSocket(); 57 | socket.bind(new InternetAddress("127.0.0.1", 10000)); 58 | socket.listen(10); 59 | 60 | while (true) 61 | { 62 | Socket accept = socket.accept(); 63 | spawn(&acceptHandler, cast(shared Socket) accept); 64 | } 65 | } 66 | 67 | void acceptHandler(shared Socket accept) 68 | { 69 | Socket socket = cast(Socket) accept; 70 | 71 | while (true) 72 | { 73 | ubyte[] data = new ubyte[1024]; 74 | long len = socket.receive(data); 75 | 76 | if (len > 0) 77 | { 78 | ubyte[] ret_data = server.Handler(data[0..len]); 79 | 80 | if (ret_data != null) 81 | { 82 | socket.send(ret_data.dup); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /libbuffer/src/buffer/compiler.d: -------------------------------------------------------------------------------- 1 | module buffer.compiler; 2 | 3 | import std.string; 4 | import std.conv; 5 | import std.array; 6 | import std.typecons; 7 | import std.algorithm.searching; 8 | import std.uni; 9 | import std.exception; 10 | 11 | template LoadBufferFile(const string fileName) 12 | { 13 | pragma(msg, "Compiling file: ", fileName, "..."); 14 | const char[] LoadBufferFile = compiler!(import(fileName)); 15 | } 16 | 17 | template LoadBufferScript(const string src) 18 | { 19 | pragma(msg, "Compiling script: ", extractScriptfragment(src), "..."); 20 | const char[] LoadBufferScript = compiler!src; 21 | } 22 | 23 | private string compiler(const string source)() 24 | { 25 | Token[] tokens = lexer(source); 26 | Sentence[] sentences = parser(tokens); 27 | 28 | Appender!string code; 29 | code.put("import buffer;\r\n\r\n"); 30 | 31 | foreach (sentence; sentences) 32 | { 33 | code.put("final static class " ~ sentence.name ~ " : Message\r\n"); 34 | code.put("{\r\n"); 35 | 36 | foreach (field; sentence.fields) 37 | { 38 | code.put("\t" ~ field.get.type ~ " " ~ field.get.name ~ ";\r\n"); 39 | } 40 | 41 | code.put("\r\n"); 42 | code.put("\tubyte[] serialize(const string method = string.init)\r\n"); 43 | code.put("\t{\r\n"); 44 | code.put("\t\treturn super.serialize!(typeof(this))(this, method);\r\n"); 45 | code.put("\t}\r\n"); 46 | code.put("}\r\n"); 47 | code.put("\r\n"); 48 | } 49 | 50 | return code.data; 51 | } 52 | 53 | /// lexer 54 | 55 | enum TokenType 56 | { 57 | Define = 1, // message 58 | Keyword = 2, // type: int8... 59 | Identifier = 3, 60 | SentenceEnd = 110, // ; 61 | DelimiterOpen = 120, // { 62 | DelimiterClose = 121 // } 63 | } 64 | 65 | private const string[] keywords = [ 66 | "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", 67 | "float32", "float64", "float128", "bool", "char", "string" 68 | ]; 69 | 70 | struct Token 71 | { 72 | TokenType type; 73 | string name; 74 | 75 | this(const string name) 76 | { 77 | if (name == "message") 78 | { 79 | this.type = TokenType.Define; 80 | } 81 | else if (keywords.any!(x => x == name)) 82 | { 83 | this.type = TokenType.Keyword; 84 | } 85 | else 86 | { 87 | this.type = TokenType.Identifier; 88 | } 89 | 90 | this.name = name; 91 | } 92 | 93 | this(const TokenType type, const string name) 94 | { 95 | this.type = type; 96 | this.name = name; 97 | } 98 | } 99 | 100 | Token[] lexer(const string source) 101 | { 102 | /* State transition diagram: 103 | 0: none 1: word 2: { 3: ; 4: } 104 | -1: / -2: // -3: /* 105 | 106 | 0 -> \s[ \f\n\r\t\v] 0 107 | -> A..Za..z_ 1 108 | -> { 2 -> add token -> 0 109 | -> ; 3 -> add token -> 0 110 | -> } 4 -> add token -> 0 111 | -> / hang state, -1 112 | -> other Exception 113 | 1 -> \s[ \f\n\r\t\v] 1 -> add token -> 0 114 | -> A..Za..z0..9_ 1 115 | -> { 1 -> add token -> 2 -> add token -> 0 116 | -> ; 1 -> add token -> 3 -> add token -> 0 117 | -> } 1 -> add token -> 4 -> add token -> 0 118 | -> / hang state, -1 119 | -> other Exception 120 | 2 -> 0 121 | 3 -> 0 122 | 4 -> 0 123 | -1 -> / -2 124 | -> * -3 125 | -> other Exception 126 | -2 -> \n restore state, hang = 0 127 | -> other skip 128 | -3 -> / if last is * then restore state & hang = 0, else skip 129 | -> other skip 130 | */ 131 | 132 | Token[] tokens; 133 | int state = 0; 134 | int stateHang; 135 | char last; 136 | string token = string.init; 137 | 138 | const string src = (cast(string) source ~ "\r\n"); 139 | foreach (i, ch; src) 140 | { 141 | switch (state) 142 | { 143 | case 0: 144 | if (isWhite(ch)) 145 | { 146 | continue; 147 | } 148 | else if (isIdentifierFirstChar(ch)) 149 | { 150 | token = ch.to!string; 151 | state = 1; 152 | } 153 | else if (ch == '{') 154 | { 155 | tokens ~= Token(TokenType.DelimiterOpen, "{"); 156 | state = 0; 157 | } 158 | else if (ch == ';') 159 | { 160 | tokens ~= Token(TokenType.SentenceEnd, ";"); 161 | state = 0; 162 | } 163 | else if (ch == '}') 164 | { 165 | tokens ~= Token(TokenType.DelimiterClose, "}"); 166 | state = 0; 167 | } 168 | else if (ch == '/') 169 | { 170 | stateHang = state; 171 | state = -1; 172 | } 173 | else 174 | { 175 | enforce(0, "Invalid character: " ~ ch.to!string); 176 | } 177 | break; 178 | case 1: 179 | if (isWhite(ch)) 180 | { 181 | tokens ~= Token(token); 182 | token = string.init; 183 | state = 0; 184 | } 185 | else if (isIdentifierChar(ch)) 186 | { 187 | token ~= ch.to!string; 188 | } 189 | else if (ch == '{') 190 | { 191 | tokens ~= Token(token); 192 | tokens ~= Token(TokenType.DelimiterOpen, "{"); 193 | token = string.init; 194 | state = 0; 195 | } 196 | else if (ch == ';') 197 | { 198 | tokens ~= Token(token); 199 | tokens ~= Token(TokenType.SentenceEnd, ";"); 200 | token = string.init; 201 | state = 0; 202 | } 203 | else if (ch == '}') 204 | { 205 | tokens ~= Token(token); 206 | tokens ~= Token(TokenType.DelimiterClose, "}"); 207 | token = string.init; 208 | state = 0; 209 | } 210 | else if (ch == '/') 211 | { 212 | stateHang = state; 213 | state = -1; 214 | } 215 | else 216 | { 217 | enforce(0, "Invalid character: " ~ ch.to!string); 218 | } 219 | break; 220 | case -1: 221 | if (ch == '/') 222 | { 223 | state = -2; 224 | } 225 | else if (ch == '*') 226 | { 227 | state = -3; 228 | } 229 | else 230 | { 231 | enforce(0, "Invalid character: " ~ ch.to!string); 232 | } 233 | break; 234 | case -2: 235 | if (ch == '\n') 236 | { 237 | state = stateHang; 238 | stateHang = 0; 239 | } 240 | break; 241 | case -3: 242 | if ((ch == '/') && (last == '*')) 243 | { 244 | state = stateHang; 245 | stateHang = 0; 246 | } 247 | break; 248 | default: 249 | break; 250 | } 251 | 252 | last = ch; 253 | } 254 | 255 | return tokens; 256 | } 257 | 258 | private bool isIdentifierFirstChar(const char ch) 259 | { 260 | return isAlpha(ch) || ch == '_'; 261 | } 262 | 263 | private bool isIdentifierChar(const char ch) 264 | { 265 | return isAlphaNum(ch) || ch == '_'; 266 | } 267 | 268 | private string extractScriptfragment(const string source) 269 | { 270 | string ret = string.init; 271 | 272 | foreach (ch; source) 273 | { 274 | if (ret.length >= 50) 275 | break; 276 | if (!isWhite(ch)) 277 | ret ~= ch.to!string; 278 | else if ((ret.length > 0) && (ret[$ - 1] != ' ')) 279 | ret ~= " "; 280 | } 281 | 282 | return ret; 283 | } 284 | 285 | /// parser 286 | 287 | struct Field 288 | { 289 | string type; 290 | string name; 291 | } 292 | 293 | struct Sentence 294 | { 295 | string name; 296 | Nullable!Field[] fields; 297 | } 298 | 299 | Sentence[] parser(const Token[] tokens) 300 | { 301 | Sentence[] sentences; 302 | int pos; 303 | while (pos < cast(int)tokens.length - 1) 304 | { 305 | if (tokens[pos].type != TokenType.Define) 306 | { 307 | enforce(0, "Syntax error at " ~ tokens[pos].name); 308 | } 309 | 310 | sentences ~= parser_define(tokens, pos); 311 | } 312 | 313 | return sentences; 314 | } 315 | 316 | private Sentence parser_define(const Token[] tokens, ref int pos) 317 | { 318 | if ((cast(int)tokens.length - pos < 4) || (tokens[pos].type != TokenType.Define) || (tokens[pos + 1].type != TokenType.Identifier) || (tokens[pos + 2].type != TokenType.DelimiterOpen)) 319 | { 320 | enforce(0, "Syntax error at " ~ tokens[pos].name); 321 | } 322 | 323 | Sentence sentence; 324 | sentence.name = tokens[pos + 1].name; 325 | pos += 3; 326 | 327 | while (pos < tokens.length) 328 | { 329 | Nullable!Field field = parser_field(tokens, pos); 330 | 331 | if (field.isNull) 332 | return sentence; 333 | 334 | sentence.fields ~= field; 335 | } 336 | 337 | return sentence; 338 | } 339 | 340 | private Nullable!Field parser_field(const Token[] tokens, ref int pos) 341 | { 342 | if ((cast(int)tokens.length - pos >= 1) && (tokens[pos].type == TokenType.DelimiterClose)) 343 | { 344 | pos++; 345 | return Nullable!Field(); 346 | } 347 | 348 | if ((cast(int)tokens.length - pos < 3) || (tokens[pos].type != TokenType.Keyword) 349 | || (tokens[pos + 1].type != TokenType.Identifier) 350 | || (tokens[pos + 2].type != TokenType.SentenceEnd)) 351 | { 352 | enforce(0, "Syntax error at " ~ tokens[pos].name); 353 | } 354 | 355 | Field field; 356 | field.type = tokens[pos].name; 357 | field.name = tokens[pos + 1].name; 358 | pos += 3; 359 | 360 | return Nullable!Field(field); 361 | } 362 | 363 | /* 364 | import buffer; 365 | 366 | final static class Sample : Message 367 | { 368 | string name; 369 | int32 age; 370 | int16 sex; 371 | 372 | ubyte[] serialize(const string method = string.init) 373 | { 374 | return super.serialize!(typeof(this))(this, method); 375 | } 376 | } 377 | */ 378 | -------------------------------------------------------------------------------- /libbuffer/src/buffer/message.d: -------------------------------------------------------------------------------- 1 | module buffer.message; 2 | 3 | import std.traits; 4 | import std.typecons; 5 | import std.variant; 6 | import std.conv : to; 7 | import std.exception; 8 | 9 | import crypto.rsa; 10 | 11 | public import buffer.compiler; 12 | public import buffer.packet; 13 | import buffer.utils; 14 | 15 | /// 16 | abstract class Message 17 | { 18 | public: 19 | 20 | alias int8 = byte; 21 | alias uint8 = ubyte; 22 | alias int16 = short; 23 | alias uint16 = ushort; 24 | alias int32 = int; 25 | alias uint32 = uint; 26 | alias int64 = long; 27 | alias uint64 = ulong; 28 | alias float32 = float; 29 | alias float64 = double; 30 | alias float128 = real; 31 | //bool 32 | //char 33 | //string 34 | 35 | /// 36 | static void settings(const ushort magic, const CryptType crypt = CryptType.NONE, const string key = string.init) 37 | { 38 | assert((crypt == CryptType.NONE) || (crypt != CryptType.NONE && key != string.init), 39 | "Must specify key when specifying the type of CryptType."); 40 | 41 | _magic = magic; 42 | _crypt = crypt; 43 | _key = key; 44 | 45 | if ((_crypt == CryptType.RSA) || (_crypt == CryptType.RSA_XTEA_MIXIN)) 46 | { 47 | _rsaKey = RSA.decodeKey(Message._key); 48 | 49 | enforce(!_rsaKey.isNull, "Rsakey is incorrect."); 50 | } 51 | } 52 | 53 | /// 54 | static void settings(const ushort magic, RSAKeyInfo rsaKey, const bool mixinXteaMode = false) 55 | { 56 | _magic = magic; 57 | _crypt = mixinXteaMode ? CryptType.RSA_XTEA_MIXIN : CryptType.RSA; 58 | _rsaKey = rsaKey; 59 | } 60 | 61 | /// 62 | static ubyte[] serialize_without_msginfo(Params...)(const string method, Params params) 63 | { 64 | return serialize_without_msginfo!(Params)(_magic, _crypt, _key, _rsaKey, method, params); 65 | } 66 | 67 | /// 68 | static ubyte[] serialize_without_msginfo(Params...)(const ushort magic, const CryptType crypt, const string key, Nullable!RSAKeyInfo rsaKey, 69 | string method, Params params) 70 | { 71 | Variant[] t_params; 72 | 73 | foreach(p; params) 74 | { 75 | t_params ~= Variant(p); 76 | } 77 | 78 | return Packet.build(magic, crypt, key, rsaKey, string.init, method, t_params); 79 | } 80 | 81 | static void getMessageInfo(const ubyte[] buffer, out string name, out string method) 82 | { 83 | Packet.parseInfo(buffer, name, method); 84 | } 85 | 86 | /// 87 | static Variant[] deserialize(const ubyte[] buffer, out string name, out string method) 88 | { 89 | return deserializeEx(_magic, _crypt, _key, _rsaKey, buffer, name, method); 90 | } 91 | 92 | /// 93 | static Variant[] deserializeEx(const ushort magic, const CryptType crypt, const string key, Nullable!RSAKeyInfo rsaKey, 94 | const ubyte[] buffer, out string name, out string method) 95 | { 96 | return Packet.parse(buffer, magic, crypt, key, rsaKey, name, method); 97 | } 98 | 99 | /// 100 | static T deserialize(T)(const ubyte[] buffer) 101 | if ((BaseTypeTuple!T.length > 0) && is(BaseTypeTuple!T[0] == Message)) 102 | { 103 | string method; 104 | 105 | return deserialize!T(buffer, method); 106 | } 107 | 108 | /// 109 | static T deserialize(T)(const ubyte[] buffer, out string method) 110 | if ((BaseTypeTuple!T.length > 0) && is(BaseTypeTuple!T[0] == Message)) 111 | { 112 | string name; 113 | const Variant[] params = deserialize(buffer, name, method); 114 | 115 | if (name == string.init || params == null) 116 | { 117 | return null; 118 | } 119 | 120 | T message = new T(); 121 | if (getClassSimpleName(T.classinfo.name) != name) 122 | { 123 | assert(0, "The type T(" ~ T.classinfo.name ~ ") of the incoming template is incorrect. It should be " ~ name); 124 | } 125 | 126 | static foreach (i, type; FieldTypeTuple!(T)) 127 | { 128 | mixin(` 129 | message.` ~ FieldNameTuple!T[i] ~ ` = params[` ~ i.to!string ~ `].get!` ~ type.stringof ~ `; 130 | `); 131 | } 132 | 133 | return message; 134 | } 135 | 136 | protected: 137 | 138 | ubyte[] serialize(T)(const T message, const string method = string.init) 139 | if ((BaseTypeTuple!T.length > 0) && is(BaseTypeTuple!T[0] == Message)) 140 | { 141 | assert(message !is null, "The object to serialize cannot be null."); 142 | 143 | Variant[] params; 144 | 145 | static foreach (i, type; FieldTypeTuple!T) 146 | { 147 | mixin(` 148 | params ~= Variant(message.` ~ FieldNameTuple!T[i] ~ `); 149 | `); 150 | } 151 | 152 | return Packet.build(_magic, _crypt, _key, _rsaKey, getClassSimpleName(T.classinfo.name), method, params); 153 | } 154 | 155 | package: 156 | 157 | __gshared ushort _magic; 158 | __gshared CryptType _crypt; 159 | __gshared string _key; 160 | __gshared Nullable!RSAKeyInfo _rsaKey; 161 | } 162 | -------------------------------------------------------------------------------- /libbuffer/src/buffer/package.d: -------------------------------------------------------------------------------- 1 | module buffer; 2 | 3 | public import buffer.message; -------------------------------------------------------------------------------- /libbuffer/src/buffer/packet.d: -------------------------------------------------------------------------------- 1 | module buffer.packet; 2 | 3 | import std.meta : AliasSeq, staticIndexOf; 4 | import std.variant; 5 | import std.bitmanip; 6 | import std.traits; 7 | import std.typecons; 8 | import std.conv : to; 9 | import std.exception; 10 | 11 | import crypto.aes; 12 | import crypto.tea; 13 | import crypto.rsa; 14 | 15 | import buffer.utils; 16 | 17 | /// All encryption supported. 18 | enum CryptType 19 | { 20 | NONE = 0, 21 | XTEA = 1, 22 | AES = 2, 23 | RSA = 3, 24 | RSA_XTEA_MIXIN = 4 25 | } 26 | 27 | package: 28 | 29 | /// These two items must correspond one by one. 30 | alias supportedBuiltinTypes = AliasSeq!( byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real, bool, char, string); 31 | immutable byte[] supportedBuiltinTypeNos = [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x20, 0x21, 0x22, 0x30, 0x40, 0x41 ]; 32 | 33 | /// Convert Type to TypeNo. 34 | template TypeNo(T) 35 | { 36 | enum idx = staticIndexOf!(T, supportedBuiltinTypes); 37 | static assert(idx != -1, "Data types that are not supported: " ~ typeid(T)); 38 | enum TypeNo = supportedBuiltinTypeNos[idx]; 39 | } 40 | 41 | alias write = std.bitmanip.write; 42 | 43 | class Packet 44 | { 45 | static ubyte[] build(const ushort magic, const CryptType crypt, const string key, Nullable!RSAKeyInfo rsaKey, 46 | const string name, const string method, Variant[] params) 47 | { 48 | assert(name.length <= 255, "Paramter name cannot be greater than 255 characters."); 49 | assert(method.length <= 255, "Paramter method cannot be greater than 255 characters."); 50 | 51 | ubyte[] tlv; 52 | BufferBuilder bb = new BufferBuilder(&tlv); 53 | 54 | foreach (v; params) 55 | { 56 | bool typeValid; 57 | static foreach (T; supportedBuiltinTypes) 58 | { 59 | if (v.type == typeid(T)) 60 | { 61 | static if (is(T == string)) 62 | bb.put!T(v.get!T, true, true, 4); 63 | else 64 | bb.put!T(v.get!T, true, false, 0); 65 | typeValid = true; 66 | } 67 | else if (v.type == typeid(ConstOf!T)) 68 | { 69 | static if (is(Unique!T == string)) 70 | bb.put!T(v.get!(const T), true, true, 4); 71 | else 72 | bb.put!T(v.get!(const T), true, false, 0); 73 | typeValid = true; 74 | } 75 | else if (v.type == typeid(SharedOf!T)) 76 | { 77 | static if (is(Unique!T == string)) 78 | bb.put!T(v.get!(shared T), true, true, 4); 79 | else 80 | bb.put!T(v.get!(shared T), true, false, 0); 81 | typeValid = true; 82 | } 83 | else if (v.type == typeid(SharedConstOf!T)) 84 | { 85 | static if (is(Unique!T == string)) 86 | bb.put!T(v.get!(shared const T), true, true, 4); 87 | else 88 | bb.put!T(v.get!(shared const T), true, false, 0); 89 | typeValid = true; 90 | } 91 | else if (v.type == typeid(ImmutableOf!T)) 92 | { 93 | static if (is(Unique!T == string)) 94 | bb.put!T(v.get!(immutable T), true, true, 4); 95 | else 96 | bb.put!T(v.get!(immutable T), true, false, 0); 97 | typeValid = true; 98 | } 99 | } 100 | 101 | assert(typeValid, "Data types id that are not supported: " ~ v.type.toString); 102 | } 103 | 104 | final switch (crypt) 105 | { 106 | case CryptType.NONE: 107 | break; 108 | case CryptType.XTEA: 109 | tlv = Xtea.encrypt(tlv, key, 64, PaddingMode.PKCS5); 110 | break; 111 | case CryptType.AES: 112 | tlv = AESUtils.encrypt!AES128(tlv, key, iv, PaddingMode.PKCS5); 113 | break; 114 | case CryptType.RSA: 115 | tlv = RSA.encrypt(rsaKey.get, tlv); 116 | break; 117 | case CryptType.RSA_XTEA_MIXIN: 118 | tlv = RSA.encrypt(rsaKey.get, tlv, true); 119 | } 120 | 121 | ubyte[] buffer; 122 | bb = new BufferBuilder(&buffer); 123 | bb.put!ushort(magic, false, false, 0); 124 | bb.put!int(0, false, false, 0); // length, seize a seat. 125 | bb.put!string(name, false, true, 2); 126 | bb.put!string(method, false, true, 2); 127 | buffer ~= tlv; 128 | buffer.write!int(cast(int)(buffer.length - 2 - 4 + 2), 2); 129 | buffer ~= strToByte_hex(MD5(buffer)[0 .. 4]); 130 | 131 | return buffer; 132 | } 133 | 134 | static size_t parseInfo(const ubyte[] buffer, out string name, out string method) 135 | { 136 | enforce(buffer != null && buffer.length >= 10, "Incorrect buffer length."); 137 | 138 | ushort len1 = buffer.peek!ushort(6); 139 | if (len1 > 0) 140 | { 141 | name = cast(string) buffer[8 .. 8 + len1]; 142 | } 143 | 144 | ushort len2 = buffer.peek!ushort(8 + len1); 145 | if (len2 > 0) 146 | { 147 | method = cast(string) buffer[10 + len1 .. 10 + len1 + len2]; 148 | } 149 | 150 | return 10 + len1 + len2; 151 | } 152 | 153 | static Variant[] parse(const ubyte[] buffer, const ushort magic, const CryptType crypt, const string key, Nullable!RSAKeyInfo rsaKey, out string name, out string method) 154 | { 155 | enforce(buffer != null && buffer.length >= 10, "Incorrect buffer length."); 156 | 157 | ushort t_magic; 158 | int t_len; 159 | t_magic = buffer.peek!ushort(0); 160 | t_len = buffer.peek!int(2); 161 | 162 | if ((t_magic != magic) || (t_len > cast(int)buffer.length - 6)) 163 | { 164 | return null; 165 | } 166 | 167 | ubyte[] buf = cast(ubyte[]) buffer[0 .. t_len + 6]; 168 | if (strToByte_hex(MD5(buf[0 .. $ - 2])[0 .. 4]) != buf[$ - 2 .. $]) 169 | { 170 | return null; 171 | } 172 | 173 | size_t tlv_pos = parseInfo(buf, name, method); 174 | buf = buf[tlv_pos .. $ - 2]; 175 | 176 | final switch (crypt) 177 | { 178 | case CryptType.NONE: 179 | break; 180 | case CryptType.XTEA: 181 | buf = Xtea.decrypt(buf, key, 64, PaddingMode.PKCS5); 182 | break; 183 | case CryptType.AES: 184 | buf = AESUtils.decrypt!AES128(buf, key, iv, PaddingMode.PKCS5); 185 | break; 186 | case CryptType.RSA: 187 | buf = RSA.decrypt(rsaKey.get, buf); 188 | break; 189 | case CryptType.RSA_XTEA_MIXIN: 190 | buf = RSA.decrypt(rsaKey.get, buf, true); 191 | break; 192 | } 193 | 194 | ubyte typeNo; 195 | int pos; 196 | Variant[] ret; 197 | 198 | while (pos < buf.length) 199 | { 200 | typeNo = buf[pos]; 201 | pos++; 202 | 203 | bool typeValid; 204 | static foreach (idx, T; supportedBuiltinTypes) 205 | { 206 | if (typeNo == supportedBuiltinTypeNos[idx]) 207 | { 208 | typeValid = true; 209 | 210 | static if (is(T == real)) 211 | { 212 | //get!real; 213 | ret ~= Variant(ubytesToReal(buf[pos .. pos + real.sizeof])); 214 | pos += real.sizeof; 215 | } 216 | else static if (is(T == string)) 217 | { 218 | immutable temp = buf.peek!int(pos); 219 | pos += 4; 220 | ret ~= Variant(cast(string) buf[pos .. pos + temp]); // !! May be: invalid UTF-8 sequence. 221 | pos += temp; 222 | } 223 | else 224 | { 225 | ret ~= Variant(buf.peek!T(pos)); 226 | pos += T.sizeof; 227 | } 228 | } 229 | } 230 | assert(typeValid, "Data types id that are not supported: " ~ typeNo.to!string); 231 | } 232 | 233 | return ret; 234 | } 235 | 236 | private: 237 | 238 | static ubyte[] iv = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 239 | } 240 | 241 | class BufferBuilder 242 | { 243 | public ubyte[]* buffer; 244 | 245 | this(ubyte[]* buffer) 246 | { 247 | this.buffer = buffer; 248 | } 249 | 250 | size_t put(T)(const T value, const bool isWriteTypeInfo, const bool isWriteLengthInfo, const int lengthBytes) 251 | { 252 | assert(lengthBytes == 0 || lengthBytes == 2 || lengthBytes == 4); 253 | 254 | ubyte[] buf_data; 255 | size_t len; 256 | 257 | if (isWriteTypeInfo) 258 | { 259 | *buffer ~= TypeNo!T; 260 | } 261 | 262 | static if (is(Unqual!T == string)) 263 | { 264 | buf_data = cast(ubyte[])value; 265 | len = buf_data.length; 266 | } 267 | else static if (is(Unqual!T == real)) 268 | { 269 | buf_data = realToUBytes(value); 270 | len = real.sizeof; 271 | } 272 | else 273 | { 274 | buf_data = new ubyte[T.sizeof]; 275 | buf_data.write!T(value, 0); 276 | len = T.sizeof; 277 | } 278 | 279 | if (isWriteLengthInfo && lengthBytes > 0) 280 | { 281 | ubyte[] buf_len = new ubyte[lengthBytes]; 282 | if (lengthBytes == 2) 283 | buf_len.write!ushort(cast(ushort)len, 0); 284 | else 285 | buf_len.write!int(cast(int)len, 0); 286 | 287 | *buffer ~= buf_len; 288 | } 289 | 290 | *buffer ~= buf_data; 291 | 292 | return len; 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /libbuffer/src/buffer/rpc/client.d: -------------------------------------------------------------------------------- 1 | module buffer.rpc.client; 2 | 3 | import core.stdc.errno; 4 | import std.meta : staticIndexOf; 5 | import std.traits; 6 | import std.conv : to; 7 | import std.variant; 8 | import std.socket; 9 | import std.bitmanip; 10 | import std.exception; 11 | import std.typecons; 12 | import std.datetime; 13 | 14 | import crypto.rsa; 15 | 16 | import buffer.message; 17 | 18 | // static if (!__traits(compiles, EWOULDBLOCK)) 19 | // { 20 | // enum EWOULDBLOCK = EAGAIN; 21 | // } 22 | 23 | /// Rpc client 24 | class Client 25 | { 26 | private __gshared string host; 27 | private __gshared ushort port; 28 | 29 | static void setServerHost(const string host, const ushort port) 30 | { 31 | Client.host = host; 32 | Client.port = port; 33 | } 34 | 35 | /++ 36 | Use global magic, host, port and other information. need call settings(), setServerHost() at before call this. 37 | When clients do not need to connect to different servers, using it can simplify calls. 38 | +/ 39 | static T call(T, Params...)(const string method, Params params) 40 | if (is(T == void) || (staticIndexOf!(T, supportedBuiltinTypes) != -1) || ((BaseTypeTuple!T.length > 0) && is(BaseTypeTuple!T[0] == Message))) 41 | { 42 | enforce(host != string.init, "Server host and port must be set."); 43 | 44 | static if (is(T == void)) 45 | { 46 | callEx!(T, Params)(host, port, Message._magic, Message._crypt, Message._key, Message._rsaKey, method, params); 47 | } 48 | else 49 | { 50 | return callEx!(T, Params)(host, port, Message._magic, Message._crypt, Message._key, Message._rsaKey, method, params); 51 | } 52 | } 53 | 54 | /++ 55 | With Server host, port, magic, cryptType, key parameters, not need call setServerHost(), settings(). 56 | When the same client needs to connect to different servers, it needs to be used. 57 | +/ 58 | static T callEx(T, Params...)(const string host, const ushort port, 59 | const ushort magic, const CryptType crypt, const string key, Nullable!RSAKeyInfo rsaKey, 60 | const string method, Params params) 61 | if (is(T == void) || (staticIndexOf!(T, supportedBuiltinTypes) != -1) || ((BaseTypeTuple!T.length > 0) && is(BaseTypeTuple!T[0] == Message))) 62 | { 63 | enforce(host != string.init, "Server host and port must be set."); 64 | enforce(method.length > 0, "Paramter method must be set."); 65 | 66 | TcpSocket socket = new TcpSocket(); 67 | socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, 30.seconds); 68 | socket.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, 30.seconds); 69 | socket.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, true); 70 | socket.connect(new InternetAddress(host, port)); 71 | 72 | static if (is(T == void)) 73 | { 74 | callEx!(T, Params)(socket, magic, crypt, key, rsaKey, method, params); 75 | } 76 | else 77 | { 78 | T result = callEx!(T, Params)(socket, magic, crypt, key, rsaKey, method, params); 79 | } 80 | 81 | socket.shutdown(SocketShutdown.BOTH); 82 | socket.close(); 83 | 84 | static if (!is(T == void)) 85 | { 86 | return result; 87 | } 88 | } 89 | 90 | /++ 91 | With Socket socket, magic, cryptType, key parameters, not need call setServerHost(), settings(). 92 | When the same client needs to connect to different servers, it needs to be used. 93 | At the same time, if long connection mode is adopted, it needs to be used as well. 94 | +/ 95 | static T callEx(T, Params...)(Socket socket, 96 | const ushort magic, const CryptType crypt, const string key, Nullable!RSAKeyInfo rsaKey, 97 | const string method, Params params) 98 | if (is(T == void) || (staticIndexOf!(T, supportedBuiltinTypes) != -1) || ((BaseTypeTuple!T.length > 0) && is(BaseTypeTuple!T[0] == Message))) 99 | { 100 | enforce(socket.isAlive, "The socket is not connected to the server."); 101 | enforce(method.length > 0, "Paramter method must be set."); 102 | 103 | static if (is(T == void)) 104 | { 105 | request(socket, false, Message.serialize_without_msginfo(magic, crypt, key, rsaKey, method, params)); 106 | return; 107 | } 108 | else 109 | { 110 | ubyte[] response = request(socket, true, Message.serialize_without_msginfo(magic, crypt, key, rsaKey, method, params)); 111 | string name; 112 | string res_method; 113 | Variant[] res_params = Message.deserializeEx(magic, crypt, key, rsaKey, response, name, res_method); 114 | 115 | //enforce(method == res_method); 116 | 117 | static if (isBuiltinType!T) 118 | { 119 | enforce(res_params.length == 1, "The number of response parameters from the server is incorrect."); 120 | 121 | return res_params[0].get!T; 122 | } 123 | else 124 | { 125 | alias FieldTypes = FieldTypeTuple!T; 126 | enforce(FieldTypes.length == res_params.length, "Incorrect number of parameters, " ~ T.stringof ~ " requires " ~ FieldTypes.length.to!string ~ " parameters."); 127 | 128 | T message = new T(); 129 | 130 | foreach (i, type; FieldTypes) 131 | { 132 | mixin(` 133 | message.` ~ FieldNameTuple!T[i] ~ ` = res_params[` ~ i.to!string ~ `].get!` ~ type.stringof ~ `; 134 | `); 135 | } 136 | 137 | return message; 138 | } 139 | } 140 | } 141 | 142 | /++ 143 | Note: 144 | The caller is responsible for connection and closure without actively closing the socket. 145 | But you need to close the exception before throwing it to release server resources. 146 | +/ 147 | private static ubyte[] request(Socket socket, const bool requireReceive, const ubyte[] data) 148 | { 149 | long len; 150 | for (size_t off; off < data.length; off += len) 151 | { 152 | len = socket.send(data[off..$]); 153 | 154 | if (len > 0) 155 | { 156 | continue; 157 | } 158 | else if (len == 0) 159 | { 160 | socket.shutdown(SocketShutdown.BOTH); 161 | socket.close(); 162 | 163 | throw new Exception("Server socket close at sending. error: " ~ formatSocketError(errno)); 164 | } 165 | else 166 | { 167 | if (errno == EINTR) // || errno == EAGAIN || errno == EWOULDBLOCK) 168 | { 169 | len = 0; 170 | continue; 171 | } 172 | 173 | socket.shutdown(SocketShutdown.BOTH); 174 | socket.close(); 175 | 176 | throw new Exception("Server socket error at sending. error: " ~ formatSocketError(errno)); 177 | } 178 | } 179 | 180 | if (!requireReceive) 181 | { 182 | return null; 183 | } 184 | 185 | ubyte[] receive(long length) 186 | { 187 | ubyte[] buf = new ubyte[cast(uint)length]; 188 | long len; 189 | 190 | for (size_t off; off < buf.length; off += len) 191 | { 192 | len = socket.receive(buf[off..$]); 193 | 194 | if (len > 0) 195 | { 196 | continue; 197 | } 198 | else if (len == 0) 199 | { 200 | return null; 201 | } 202 | else 203 | { 204 | if (errno == EINTR) // || errno == EAGAIN || errno == EWOULDBLOCK) 205 | { 206 | len = 0; 207 | continue; 208 | } 209 | 210 | return null; 211 | } 212 | } 213 | 214 | return buf; 215 | } 216 | 217 | len = cast(long)(ushort.sizeof + int.sizeof); 218 | ubyte[] buffer = receive(len); 219 | 220 | if (buffer.length != len) 221 | { 222 | socket.shutdown(SocketShutdown.BOTH); 223 | socket.close(); 224 | 225 | throw new Exception("Server socket error at receiving. error: " ~ formatSocketError(errno)); 226 | } 227 | 228 | len = buffer.peek!int(2); 229 | ubyte[] buf = receive(len); 230 | 231 | if (buf.length != len) 232 | { 233 | socket.shutdown(SocketShutdown.BOTH); 234 | socket.close(); 235 | 236 | throw new Exception("Server socket error at receiving. error: " ~ formatSocketError(errno)); 237 | } 238 | 239 | return buffer ~ buf; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /libbuffer/src/buffer/rpc/server.d: -------------------------------------------------------------------------------- 1 | module buffer.rpc.server; 2 | 3 | import std.meta : staticIndexOf; 4 | import std.traits; 5 | import std.algorithm.searching; 6 | import std.conv : to; 7 | import std.variant; 8 | 9 | import buffer.message; 10 | 11 | class Server(Business) 12 | { 13 | static immutable string[] builtinFunctions = [ "__ctor", "__dtor", "opEquals", "opCmp", "toHash", "toString", "Monitor", "factory" ]; 14 | private Business business = new Business(); 15 | 16 | this() 17 | { 18 | business = new Business(); 19 | } 20 | 21 | ubyte[] Handler(string Package = string.init, Stuff...)(const ubyte[] data, Stuff stuff) 22 | { 23 | string name; 24 | string method; 25 | Variant[] params = Message.deserialize(data, name, method); 26 | foreach (s; stuff) 27 | { 28 | params ~= Variant(s); 29 | } 30 | 31 | foreach (member; __traits(allMembers, Business)) 32 | { 33 | alias funcs = MemberFunctionsTuple!(Business, member); 34 | 35 | static if (funcs.length > 0 && !canFind(builtinFunctions, member)) 36 | { 37 | static assert(funcs.length == 1, "The function of RPC call doesn't allow the overloads, function: " ~ member); 38 | 39 | alias func = typeof(funcs[0]); 40 | alias ParameterTypes = ParameterTypeTuple!func; 41 | alias T = ReturnType!func; 42 | 43 | static assert(is(T == void) || (staticIndexOf!(T, supportedBuiltinTypes) != -1) || ((BaseTypeTuple!T.length > 0) && is(BaseTypeTuple!T[0] == Message)), 44 | "The function of RPC call return type is incorrect, function: " ~ member); 45 | 46 | static if (Package != string.init) 47 | { 48 | mixin("import " ~ Package ~ ";"); 49 | } 50 | 51 | static if (is(T == void)) 52 | { 53 | mixin(` 54 | if (method == "` ~ member ~ `") 55 | { 56 | if (params.length < ` ~ ParameterTypes.length.to!string ~ `) 57 | { 58 | import std.stdio : writeln; 59 | writeln("Incorrect number of parameters, ` ~ member ~ ` requires ` ~ ParameterTypes.length.to!string ~ ` parameters."); 60 | assert(0, "Incorrect number of parameters, ` ~ member ~ ` requires ` ~ ParameterTypes.length.to!string ~ ` parameters."); 61 | } 62 | 63 | business.` ~ member ~ `(` ~ combineParams!ParameterTypes ~ `); 64 | return null; 65 | } 66 | `); 67 | } 68 | else static if (isBuiltinType!T) 69 | { 70 | mixin(` 71 | if (method == "` ~ member ~ `") 72 | { 73 | if (params.length < ` ~ ParameterTypes.length.to!string ~ `) 74 | { 75 | import std.stdio : writeln; 76 | writeln("Incorrect number of parameters, ` ~ member ~ ` requires ` ~ ParameterTypes.length.to!string ~ ` parameters."); 77 | assert(0, "Incorrect number of parameters, ` ~ member ~ ` requires ` ~ ParameterTypes.length.to!string ~ ` parameters."); 78 | } 79 | 80 | T ret = business.` ~ member ~ `(` ~ combineParams!ParameterTypes ~ `); 81 | return Message.serialize_without_msginfo(method, ret); 82 | } 83 | `); 84 | } 85 | else 86 | { 87 | mixin(` 88 | if (method == "` ~ member ~ `") 89 | { 90 | if (params.length < ` ~ ParameterTypes.length.to!string ~ `) 91 | { 92 | import std.stdio : writeln; 93 | writeln("Incorrect number of parameters, ` ~ member ~ ` requires ` ~ ParameterTypes.length.to!string ~ ` parameters."); 94 | assert(0, "Incorrect number of parameters, ` ~ member ~ ` requires ` ~ ParameterTypes.length.to!string ~ ` parameters."); 95 | } 96 | 97 | T ret = business.` ~ member ~ `(` ~ combineParams!ParameterTypes ~ `); 98 | return ((ret is null) ? null : ret.serialize()); 99 | } 100 | `); 101 | } 102 | } 103 | } 104 | 105 | // Server does not implement client call method. 106 | return null; 107 | } 108 | 109 | private static string combineParams(ParameterTypes...)() 110 | { 111 | string s; 112 | 113 | foreach (i, type; ParameterTypes) 114 | { 115 | if (i > 0) s ~= ", "; 116 | 117 | s ~= ("params[" ~ i.to!string ~ "].get!" ~ type.stringof); 118 | } 119 | 120 | return s; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /libbuffer/src/buffer/utils.d: -------------------------------------------------------------------------------- 1 | module buffer.utils; 2 | 3 | import std.conv; 4 | import std.string; 5 | import std.digest.md; 6 | import std.array; 7 | 8 | string MD5(scope const(void[])[] src...) 9 | { 10 | auto md5 = new MD5Digest(); 11 | ubyte[] hash = md5.digest(src); 12 | 13 | return toHexString(hash).toUpper(); 14 | } 15 | 16 | ubyte[] strToByte_hex(const string input) 17 | { 18 | Appender!(ubyte[]) app; 19 | 20 | for (int i; i < input.length; i += 2) 21 | { 22 | app.put(input[i .. i + 2].to!ubyte(16)); 23 | } 24 | 25 | return app.data; 26 | } 27 | 28 | string byteToStr_hex(T = byte)(const T[] buffer) 29 | { 30 | Appender!string app; 31 | 32 | foreach (b; buffer) 33 | { 34 | app.put(rightJustify(b.to!string(16).toUpper(), 2, '0')); 35 | } 36 | return app.data; 37 | } 38 | 39 | string getClassSimpleName(const string input) 40 | { 41 | int pos = cast(int)lastIndexOf(input, '.'); 42 | 43 | return input[pos < 0 ? 0 : pos + 1 .. $]; 44 | } 45 | 46 | ubyte[] realToUBytes(scope const real value) 47 | { 48 | ubyte[] buf = new ubyte[real.sizeof]; 49 | ubyte* p = cast(ubyte*)&value; 50 | int i = real.sizeof; 51 | 52 | while (i-- > 0) 53 | { 54 | buf[real.sizeof - i - 1] = p[i]; 55 | } 56 | 57 | return buf; 58 | } 59 | 60 | real ubytesToReal(scope const ubyte[] value) 61 | { 62 | real r; 63 | ubyte* p = cast(ubyte*)&r; 64 | 65 | for (int i = 0; i < value.length; i++) 66 | { 67 | p[value.length - i - 1] = value[i]; 68 | } 69 | 70 | return r; 71 | } 72 | -------------------------------------------------------------------------------- /other_client/C/README.md: -------------------------------------------------------------------------------- 1 | # A simple and practical protocol buffer & RPC library. 2 | 3 | At present, it has only dlang and C++ implementations, and will add more language support in the future, such as: Lua, Java, python, ruby, golang, rust... 4 | 5 | C++ project on github: 6 | https://github.com/shove70/shove.c 7 | 8 | 9 | ### Quick Start: 10 | 11 | ``` 12 | Sample sample; 13 | sample.id = 1; 14 | sample.name = "abcde"; 15 | sample.age = 10; 16 | 17 | vector serialized; 18 | sample.serialize(serialized); 19 | 20 | Sample sample2 = Message::deserialize(serialized.data(), serialized.size()); 21 | cout << sample2.id << ", " << sample2.name << ", " << sample2.age << endl; 22 | 23 | ////////////////////// 24 | 25 | Client::bindTcpRequestHandler(&tcpRequestHandler); 26 | int r = Client::call("Login", 101, "abcde"); 27 | cout << r << endl; 28 | 29 | Sample sample3 = Client::call("Login", 1, "abcde", 10); 30 | cout << sample3.id << ", " << sample3.name << ", " << sample3.age << endl; 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /other_client/C/compiler/.gitignore: -------------------------------------------------------------------------------- 1 | /buffc 2 | /buffc.exe 3 | -------------------------------------------------------------------------------- /other_client/C/compiler/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Shove" 4 | ], 5 | "name": "buffc", 6 | "targetType": "executable", 7 | "license": "MIT", 8 | "copyright": "Copyright © 2017-2020, Shove", 9 | "targetName": "buffc", 10 | "description": "buffc compiler.", 11 | "dependencies": { 12 | "buffer": { 13 | "path": "../../../" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /other_client/C/compiler/src/app.d: -------------------------------------------------------------------------------- 1 | import std.array; 2 | import std.stdio; 3 | import std.file; 4 | import std.path; 5 | import std.algorithm; 6 | import std.conv; 7 | import std.string; 8 | import std.uni; 9 | 10 | import buffer.compiler; 11 | 12 | void main(string[] args) 13 | { 14 | if (args.length < 3) 15 | { 16 | writeln("Usage: ./buffc src_path dst_path"); 17 | return; 18 | } 19 | 20 | string src = args[1]; 21 | string dst = args[2]; 22 | 23 | if (!std.file.exists(src)) 24 | { 25 | writeln("The src path" ~ src ~ " not exists."); 26 | return; 27 | } 28 | 29 | if (std.file.isFile(src)) 30 | { 31 | writeln("The src path" ~ src ~ " is a file, not a path."); 32 | return; 33 | } 34 | 35 | if (std.file.exists(dst) && std.file.isFile(dst)) 36 | { 37 | writeln("The dst path" ~ src ~ " is a file, not a path."); 38 | return; 39 | } 40 | 41 | string libIncludePath = "{ Replace the real path }"; 42 | 43 | if (args.length >= 4) 44 | { 45 | libIncludePath = args[3]; 46 | } 47 | 48 | std.file.mkdirRecurse(dst); 49 | 50 | foreach (DirEntry e; dirEntries(src, SpanMode.shallow).filter!(a => a.isFile)) 51 | { 52 | string srcFile = e.name; 53 | if (toLower(std.path.extension(srcFile)) != ".buffer") 54 | { 55 | continue; 56 | } 57 | string fileName = baseName(e.name, ".buffer"); 58 | string dstFilename = buildPath(dst, fileName ~ ".h"); 59 | 60 | Token[] tokens = lexer(std.file.readText(e.name)); 61 | Sentence[] sentences = parser(tokens); 62 | 63 | Appender!string code; 64 | code.put("#pragma once\r\n\r\n"); 65 | code.put("#include \r\n"); 66 | code.put("#include \"" ~ libIncludePath ~ "/message.h\"\r\n\r\n"); 67 | code.put("using namespace std;\r\n"); 68 | code.put("using namespace shove::buffer;\r\n\r\n"); 69 | 70 | foreach (sentence; sentences) 71 | { 72 | code.put("class " ~ sentence.name ~ " : public Message\r\n"); 73 | code.put("{\r\n"); 74 | code.put("public:\r\n"); 75 | code.put("\tstring _className() { return \"" ~ sentence.name ~ "\"; }\r\n\r\n"); 76 | 77 | foreach (field; sentence.fields) 78 | { 79 | code.put("\t" ~ field.type ~ " " ~ field.name ~ ";\r\n"); 80 | } 81 | 82 | code.put("\r\n"); 83 | code.put("\tvoid setValue(vector& params)\r\n"); 84 | code.put("\t{\r\n"); 85 | 86 | foreach (i, field; sentence.fields) 87 | { 88 | code.put("\t\t" ~ field.name ~ " = params[" ~ i.to!string ~ "].cast<" ~ field.type ~ ">();\r\n"); 89 | } 90 | 91 | code.put("\t}\r\n\r\n"); 92 | code.put("\tvoid serialize(vector& buffer, string method = \"\")\r\n"); 93 | code.put("\t{\r\n"); 94 | code.put("\t\tMessage::serialize(buffer, \"" ~ sentence.name ~ "\", method"); 95 | 96 | foreach (i, field; sentence.fields) 97 | { 98 | code.put(", " ~ field.name); 99 | } 100 | 101 | code.put(");\r\n"); 102 | code.put("\t}\r\n"); 103 | code.put("};\r\n\r\n"); 104 | } 105 | 106 | std.file.write(dstFilename, cast(ubyte[])code.data); 107 | } 108 | } --------------------------------------------------------------------------------