1 /* 2 * 3 * Copyright 2015 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 #include <grpc/support/port_platform.h> 20 21 #include <stdio.h> 22 #include <string.h> 23 24 #include <grpc/support/alloc.h> 25 #include <grpc/support/log.h> 26 #include <grpc/support/string_util.h> 27 28 #include "src/core/ext/filters/client_channel/method_params.h" 29 #include "src/core/lib/channel/status_util.h" 30 #include "src/core/lib/gpr/string.h" 31 #include "src/core/lib/gprpp/memory.h" 32 33 // As per the retry design, we do not allow more than 5 retry attempts. 34 #define MAX_MAX_RETRY_ATTEMPTS 5 35 36 namespace grpc_core { 37 namespace internal { 38 39 namespace { 40 41 bool ParseWaitForReady( 42 grpc_json* field, ClientChannelMethodParams::WaitForReady* wait_for_ready) { 43 if (field->type != GRPC_JSON_TRUE && field->type != GRPC_JSON_FALSE) { 44 return false; 45 } 46 *wait_for_ready = field->type == GRPC_JSON_TRUE 47 ? ClientChannelMethodParams::WAIT_FOR_READY_TRUE 48 : ClientChannelMethodParams::WAIT_FOR_READY_FALSE; 49 return true; 50 } 51 52 // Parses a JSON field of the form generated for a google.proto.Duration 53 // proto message, as per: 54 // https://developers.google.com/protocol-buffers/docs/proto3#json 55 bool ParseDuration(grpc_json* field, grpc_millis* duration) { 56 if (field->type != GRPC_JSON_STRING) return false; 57 size_t len = strlen(field->value); 58 if (field->value[len - 1] != 's') return false; 59 UniquePtr<char> buf(gpr_strdup(field->value)); 60 *(buf.get() + len - 1) = '\0'; // Remove trailing 's'. 61 char* decimal_point = strchr(buf.get(), '.'); 62 int nanos = 0; 63 if (decimal_point != nullptr) { 64 *decimal_point = '\0'; 65 nanos = gpr_parse_nonnegative_int(decimal_point + 1); 66 if (nanos == -1) { 67 return false; 68 } 69 int num_digits = static_cast<int>(strlen(decimal_point + 1)); 70 if (num_digits > 9) { // We don't accept greater precision than nanos. 71 return false; 72 } 73 for (int i = 0; i < (9 - num_digits); ++i) { 74 nanos *= 10; 75 } 76 } 77 int seconds = 78 decimal_point == buf.get() ? 0 : gpr_parse_nonnegative_int(buf.get()); 79 if (seconds == -1) return false; 80 *duration = seconds * GPR_MS_PER_SEC + nanos / GPR_NS_PER_MS; 81 return true; 82 } 83 84 UniquePtr<ClientChannelMethodParams::RetryPolicy> ParseRetryPolicy( 85 grpc_json* field) { 86 auto retry_policy = MakeUnique<ClientChannelMethodParams::RetryPolicy>(); 87 if (field->type != GRPC_JSON_OBJECT) return nullptr; 88 for (grpc_json* sub_field = field->child; sub_field != nullptr; 89 sub_field = sub_field->next) { 90 if (sub_field->key == nullptr) return nullptr; 91 if (strcmp(sub_field->key, "maxAttempts") == 0) { 92 if (retry_policy->max_attempts != 0) return nullptr; // Duplicate. 93 if (sub_field->type != GRPC_JSON_NUMBER) return nullptr; 94 retry_policy->max_attempts = gpr_parse_nonnegative_int(sub_field->value); 95 if (retry_policy->max_attempts <= 1) return nullptr; 96 if (retry_policy->max_attempts > MAX_MAX_RETRY_ATTEMPTS) { 97 gpr_log(GPR_ERROR, 98 "service config: clamped retryPolicy.maxAttempts at %d", 99 MAX_MAX_RETRY_ATTEMPTS); 100 retry_policy->max_attempts = MAX_MAX_RETRY_ATTEMPTS; 101 } 102 } else if (strcmp(sub_field->key, "initialBackoff") == 0) { 103 if (retry_policy->initial_backoff > 0) return nullptr; // Duplicate. 104 if (!ParseDuration(sub_field, &retry_policy->initial_backoff)) { 105 return nullptr; 106 } 107 if (retry_policy->initial_backoff == 0) return nullptr; 108 } else if (strcmp(sub_field->key, "maxBackoff") == 0) { 109 if (retry_policy->max_backoff > 0) return nullptr; // Duplicate. 110 if (!ParseDuration(sub_field, &retry_policy->max_backoff)) { 111 return nullptr; 112 } 113 if (retry_policy->max_backoff == 0) return nullptr; 114 } else if (strcmp(sub_field->key, "backoffMultiplier") == 0) { 115 if (retry_policy->backoff_multiplier != 0) return nullptr; // Duplicate. 116 if (sub_field->type != GRPC_JSON_NUMBER) return nullptr; 117 if (sscanf(sub_field->value, "%f", &retry_policy->backoff_multiplier) != 118 1) { 119 return nullptr; 120 } 121 if (retry_policy->backoff_multiplier <= 0) return nullptr; 122 } else if (strcmp(sub_field->key, "retryableStatusCodes") == 0) { 123 if (!retry_policy->retryable_status_codes.Empty()) { 124 return nullptr; // Duplicate. 125 } 126 if (sub_field->type != GRPC_JSON_ARRAY) return nullptr; 127 for (grpc_json* element = sub_field->child; element != nullptr; 128 element = element->next) { 129 if (element->type != GRPC_JSON_STRING) return nullptr; 130 grpc_status_code status; 131 if (!grpc_status_code_from_string(element->value, &status)) { 132 return nullptr; 133 } 134 retry_policy->retryable_status_codes.Add(status); 135 } 136 if (retry_policy->retryable_status_codes.Empty()) return nullptr; 137 } 138 } 139 // Make sure required fields are set. 140 if (retry_policy->max_attempts == 0 || retry_policy->initial_backoff == 0 || 141 retry_policy->max_backoff == 0 || retry_policy->backoff_multiplier == 0 || 142 retry_policy->retryable_status_codes.Empty()) { 143 return nullptr; 144 } 145 return retry_policy; 146 } 147 148 } // namespace 149 150 RefCountedPtr<ClientChannelMethodParams> 151 ClientChannelMethodParams::CreateFromJson(const grpc_json* json) { 152 RefCountedPtr<ClientChannelMethodParams> method_params = 153 MakeRefCounted<ClientChannelMethodParams>(); 154 for (grpc_json* field = json->child; field != nullptr; field = field->next) { 155 if (field->key == nullptr) continue; 156 if (strcmp(field->key, "waitForReady") == 0) { 157 if (method_params->wait_for_ready_ != WAIT_FOR_READY_UNSET) { 158 return nullptr; // Duplicate. 159 } 160 if (!ParseWaitForReady(field, &method_params->wait_for_ready_)) { 161 return nullptr; 162 } 163 } else if (strcmp(field->key, "timeout") == 0) { 164 if (method_params->timeout_ > 0) return nullptr; // Duplicate. 165 if (!ParseDuration(field, &method_params->timeout_)) return nullptr; 166 } else if (strcmp(field->key, "retryPolicy") == 0) { 167 if (method_params->retry_policy_ != nullptr) { 168 return nullptr; // Duplicate. 169 } 170 method_params->retry_policy_ = ParseRetryPolicy(field); 171 if (method_params->retry_policy_ == nullptr) return nullptr; 172 } 173 } 174 return method_params; 175 } 176 177 } // namespace internal 178 } // namespace grpc_core 179