Home | History | Annotate | Download | only in client_channel
      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