1 module hunt.database.driver.mysql.impl.codec.ChangeUserCommandCodec;
2 
3 import hunt.database.driver.mysql.impl.codec.CapabilitiesFlag;
4 import hunt.database.driver.mysql.impl.codec.CommandCodec;
5 import hunt.database.driver.mysql.impl.codec.CommandType;
6 import hunt.database.driver.mysql.impl.codec.MySQLEncoder;
7 import hunt.database.driver.mysql.impl.codec.Packets;
8 
9 import hunt.database.driver.mysql.impl.MySQLCollation;
10 import hunt.database.driver.mysql.impl.command.ChangeUserCommand;
11 import hunt.database.driver.mysql.impl.util.BufferUtils;
12 import hunt.database.driver.mysql.impl.util.Native41Authenticator;
13 import hunt.database.base.impl.command.CommandResponse;
14 
15 import hunt.collection.Map;
16 import hunt.Exceptions;
17 import hunt.logging;
18 import hunt.net.buffer;
19 import hunt.Object;
20 import hunt.text.Charset;
21 
22 import std.array;
23 
24 /**
25  * 
26  */
27 class ChangeUserCommandCodec : CommandCodec!(Void, ChangeUserCommand) {
28     this(ChangeUserCommand cmd) {
29         super(cmd);
30     }
31 
32     override
33     void encode(MySQLEncoder encoder) {
34         super.encode(encoder);
35         sendChangeUserCommand();
36     }
37 
38     override
39     void decodePayload(ByteBuf payload, int payloadLength, int sequenceId) {
40         Packets header = cast(Packets)payload.getUnsignedByte(payload.readerIndex());
41         switch (header) {
42             case Packets.EOF_PACKET_HEADER: // 0xFE
43                 string pluginName = BufferUtils.readNullTerminatedString(payload, StandardCharsets.UTF_8);
44                 if (pluginName == "caching_sha2_password") {
45                     // TODO support different auth methods later
46                     completionHandler(failedResponse!(Void)(new 
47                         UnsupportedOperationException("unsupported authentication method: " ~ pluginName)));
48                     return;
49                 }
50                 byte[] scramble = new byte[20];
51                 payload.readBytes(scramble);
52                 byte[] scrambledPassword = Native41Authenticator.encode(cmd.password(), StandardCharsets.UTF_8, scramble);
53                 sendAuthSwitchResponse(scrambledPassword);
54                 break;
55             case Packets.OK_PACKET_HEADER:
56                 completionHandler(succeededResponse(cast(Void)null));
57                 break;
58             case Packets.ERROR_PACKET_HEADER:
59                 handleErrorPacketPayload(payload);
60                 break;
61             
62             default:
63                 warningf("Can't handle %d", header);
64                 break;
65         }
66     }
67 
68     private void sendChangeUserCommand() {
69         ByteBuf packet = allocateBuffer();
70         // encode packet header
71         int packetStartIdx = packet.writerIndex();
72         packet.writeMediumLE(0); // will set payload length later by calculation
73         packet.writeByte(sequenceId);
74 
75         // encode packet payload
76         packet.writeByte(CommandType.COM_CHANGE_USER);
77         BufferUtils.writeNullTerminatedString(packet, cmd.username(), StandardCharsets.UTF_8);
78         string password = cmd.password();
79         if (password.empty()) {
80             packet.writeByte(0);
81         } else {
82             packet.writeByte(cast(int)password.length);
83             packet.writeCharSequence(password, StandardCharsets.UTF_8);
84         }
85         BufferUtils.writeNullTerminatedString(packet, cmd.database(), StandardCharsets.UTF_8);
86         MySQLCollation collation = cmd.collation();
87         int collationId = collation.collationId();
88         encoder.charset = collation.mappedCharsetName(); // Charset.forName(collation.mappedCharsetName());
89         packet.writeShortLE(collationId);
90 
91         if ((encoder.clientCapabilitiesFlag & CapabilitiesFlag.CLIENT_PLUGIN_AUTH) != 0) {
92             BufferUtils.writeNullTerminatedString(packet, "mysql_native_password", StandardCharsets.UTF_8);
93         }
94         Map!(string, string) clientConnectionAttributes = cmd.connectionAttributes();
95         if (clientConnectionAttributes !is null && !clientConnectionAttributes.isEmpty()) {
96             encoder.clientCapabilitiesFlag |= CapabilitiesFlag.CLIENT_CONNECT_ATTRS;
97         }
98         if ((encoder.clientCapabilitiesFlag & CapabilitiesFlag.CLIENT_CONNECT_ATTRS) != 0) {
99             ByteBuf kv = allocateBuffer();
100             foreach (string key, string value ; clientConnectionAttributes) {
101                 BufferUtils.writeLengthEncodedString(kv, key, StandardCharsets.UTF_8);
102                 BufferUtils.writeLengthEncodedString(kv, value, StandardCharsets.UTF_8);
103             }
104             BufferUtils.writeLengthEncodedInteger(packet, kv.readableBytes());
105             packet.writeBytes(kv);
106         }
107 
108         // set payload length
109         int lenOfPayload = packet.writerIndex() - packetStartIdx - 4;
110         packet.setMediumLE(packetStartIdx, lenOfPayload);
111 
112         sendPacket(packet, lenOfPayload);
113     }
114 
115     private void sendAuthSwitchResponse(byte[] responseData) {
116         int payloadLength = cast(int)responseData.length;
117         ByteBuf packet = allocateBuffer(payloadLength + 4);
118         // encode packet header
119         packet.writeMediumLE(payloadLength);
120         packet.writeByte(sequenceId);
121 
122         // encode packet payload
123         packet.writeBytes(responseData);
124 
125         sendNonSplitPacket(packet);
126     }
127 }