You will be provided with a partial code base and an issue statement explaining a problem to resolve.
<issue>
Application Configuration: Attenuation Duration
The RKI requested weighting parameters for the three buckets defined by the attenuation duration threshold. Add this to the attenuation duration configuration.

@michael-burwig 

```yaml
attenuation-duration:
  thresholds:
    lower: 50
    upper: 70
  weights:
    low: 1 # attenuation < 50 dB 
    mid: 0.5 # attenuation >50 dB && < 70 dB 
    high: 0 # attenuation > 70dB is ignored
```

</issue>

<code>
[start of README.md]
1 <!-- markdownlint-disable MD041 -->
2 <h1 align="center">
3     Corona-Warn-App Server
4 </h1>
5 
6 <p align="center">
7     <a href="https://github.com/corona-warn-app/cwa-server/commits/" title="Last Commit"><img src="https://img.shields.io/github/last-commit/corona-warn-app/cwa-server?style=flat"></a>
8     <a href="https://github.com/corona-warn-app/cwa-server/issues" title="Open Issues"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-server?style=flat"></a>
9     <a href="https://circleci.com/gh/corona-warn-app/cwa-server" title="Build Status"><img src="https://circleci.com/gh/corona-warn-app/cwa-server.svg?style=shield&circle-token=4ab059989d10709df19eb4b98ab7c121a25e981a"></a>
10         <a href="https://sonarcloud.io/dashboard?id=corona-warn-app_cwa-server" title="Quality Gate"><img src="https://sonarcloud.io/api/project_badges/measure?project=corona-warn-app_cwa-server&metric=alert_status"></a>
11         <a href="https://sonarcloud.io/component_measures?id=corona-warn-app_cwa-server&metric=Coverage&view=list" title="Coverage"><img src="https://sonarcloud.io/api/project_badges/measure?project=corona-warn-app_cwa-server&metric=coverage"></a>
12     <a href="https://github.com/corona-warn-app/cwa-server/blob/master/LICENSE" title="License"><img src="https://img.shields.io/badge/License-Apache%202.0-green.svg?style=flat"></a>
13 </p>
14 
15 <p align="center">
16   <a href="#development">Development</a> •
17   <a href="#service-apis">Service APIs</a> •
18   <a href="#documentation">Documentation</a> •
19   <a href="#support-and-feedback">Support</a> •
20   <a href="#how-to-contribute">Contribute</a> •
21   <a href="#contributors">Contributors</a> •
22   <a href="#repositories">Repositories</a> •
23   <a href="#licensing">Licensing</a>
24 </p>
25 
26 The goal of this project is to develop the official Corona-Warn-App for Germany based on the exposure notification API from [Apple](https://www.apple.com/covid19/contacttracing/) and [Google](https://www.google.com/covid19/exposurenotifications/). The apps (for both iOS and Android) use Bluetooth technology to exchange anonymous encrypted data with other mobile phones (on which the app is also installed) in the vicinity of an app user's phone. The data is stored locally on each user's device, preventing authorities or other parties from accessing or controlling the data. This repository contains the **implementation of the server for encryption keys** for the Corona-Warn-App. This implementation is still a **work in progress**, and the code it contains is currently alpha-quality code.
27 
28 In this documentation, Corona-Warn-App services are also referred to as CWA services.
29 
30 ## Architecture Overview
31 
32 You can find the architecture overview [here](/docs/architecture-overview.md), which will give you
33 a good starting point in how the backend services interact with other services, and what purpose
34 they serve.
35 
36 ## Development
37 
38 After you've checked out this repository, you can run the application in one of the following ways:
39 
40 * As a [Docker](https://www.docker.com/)-based deployment on your local machine. You can run either:
41   * Single components using the respective Dockerfile or
42   * The full backend using the Docker Compose (which is considered the most convenient way)
43 * As a [Maven](https://maven.apache.org)-based build on your local machine.
44   If you want to develop something in a single component, this approach is preferable.
45 
46 ### Docker-Based Deployment
47 
48 If you want to use Docker-based deployment, you need to install Docker on your local machine. For more information about downloading and installing Docker, see the [official Docker documentation](https://docs.docker.com/get-docker/).
49 
50 #### Running the Full CWA Backend Using Docker Compose
51 
52 For your convenience, a full setup including the generation of test data has been prepared using [Docker Compose](https://docs.docker.com/compose/reference/overview/). To build the backend services, run ```docker-compose build``` in the repository's root directory. A default configuration file can be found under ```.env```in the root folder of the repository. The default values for the local Postgres and Zenko Cloudserver should be changed in this file before docker-compose is run.
53 
54 Once the services are built, you can start the whole backend using ```docker-compose up```.
55 The distribution service runs once and then finishes. If you want to trigger additional distribution runs, run ```docker-compose run distribution```.
56 
57 The docker-compose contains the following services:
58 
59 Service           | Description | Endpoint and Default Credentials
60 ------------------|-------------|-----------
61 submission        | The Corona-Warn-App submission service                                                      | `http://localhost:8000` <br> `http://localhost:8006` (for actuator endpoint)
62 distribution      | The Corona-Warn-App distribution service                                                    | NO ENDPOINT
63 postgres          | A [postgres] database installation                                                          | `postgres:8001` <br> Username: postgres <br> Password: postgres
64 pgadmin           | A [pgadmin](https://www.pgadmin.org/) installation for the postgres database                | `http://localhost:8002` <br> Username: user@domain.com <br> Password: password
65 cloudserver       | [Zenko CloudServer] is a S3-compliant object store  | `http://localhost:8003/` <br> Access key: accessKey1 <br> Secret key: verySecretKey1
66 verification-fake | A very simple fake implementation for the tan verification.                                 | `http://localhost:8004/version/v1/tan/verify` <br> The only valid tan is "edc07f08-a1aa-11ea-bb37-0242ac130002"
67 
68 ##### Known Limitation
69 
70 In rare cases the docker-compose runs into a timing issue if the distribution service starts before the bucket of the objectstore was created. This is not a big issue as you can simply run ```docker-compose run distribution``` to trigger additional distribution runs after the objectstore was initialized.
71 
72 #### Running Single CWA Services Using Docker
73 
74 If you would like to build and run a single CWA service, it's considered easiest to run them in a Docker environment. You can do this using the script provided in the respective CWA service directory. The Docker script first builds the CWA service and then creates an image for the runtime, which means that there are no additional dependencies for you to install.
75 
76 To build and run the distribution service, run the following command:
77 
78 ```bash
79 ./services/distribution/build_and_run.sh
80 ```
81 
82 To build and run the submission service, run the following command:
83 
84 ```bash
85 ./services/submission/build_and_run.sh
86 ```
87 
88 The submission service is available on [localhost:8080](http://localhost:8080 ).
89 
90 ### Maven-Based Build
91 
92 If you want to actively develop in one of the CWA services, the Maven-based runtime is most suitable.
93 To prepare your machine to run the CWA project locally, we recommend that you first ensure that you've installed the following:
94 
95 * Minimum JDK Version 11: [OpenJDK](https://openjdk.java.net/) / [SapMachine](https://sap.github.io/SapMachine/)
96 * [Maven 3.6](https://maven.apache.org/)
97 * [Postgres]
98 * [Zenko CloudServer]
99 
100 #### Configure
101 
102 After you made sure that the specified dependencies are running, configure them in the respective configuration files.
103 
104 * Configure the Postgres connection in the [submission config](./services/submission/src/main/resources/application.yaml) and in the [distribution config](./services/distribution/src/main/resources/application.yaml)
105 * Configure the S3 compatible object storage in the [distribution config](./services/distribution/src/main/resources/application.yaml)
106 * Configure the private key for the distribution service, the path need to be prefixed with `file:`
107   * `VAULT_FILESIGNING_SECRET` should be the path to the private key, example available in `<repo-root>/docker-compose-test-secrets/private.pem`
108 
109 #### Build
110 
111 After you've checked out the repository, to build the project, run ```mvn install``` in your base directory.
112 
113 #### Run
114 
115 Navigate to the service you want to start and run the spring-boot:run target. The configured Postgres and the configured S3 compliant object storage are used as default. When you start the submission service, the endpoint is available on your local port 8080.
116 
117 If you want to start the submission service, for example, you start it as follows:
118 
119 ```bash
120   cd services/submission/
121   mvn spring-boot:run
122 ```
123 
124 #### Debugging
125 
126 To enable the `DEBUG` log level, you can run the application using the Spring `dev` profile.
127 
128 ```bash
129 mvn spring-boot:run -Dspring-boot.run.profiles=dev
130 ```
131 
132 To be able to set breakpoints (e.g. in IntelliJ), it may be necessary to use the ```-Dspring-boot.run.fork=false``` parameter.
133 
134 ## Service APIs
135 
136 The API that is being exposed by the backend services is documented in an [OpenAPI](https://www.openapis.org/) specification. The specification files are available at the following locations:
137 
138 Service      | OpenAPI Specification
139 -------------|-------------
140 Submission Service        | [services/submission/api_v1.json](https://github.com/corona-warn-app/cwa-server/raw/master/services/submission/api_v1.json)
141 Distribution Service      | [services/distribution/api_v1.json](https://github.com/corona-warn-app/cwa-server/raw/master/services/distribution/api_v1.json)
142 
143 ## Spring Profiles
144 
145 ### Distribution
146 
147 Profile          | Effect
148 -----------------|-------------
149 `dev`            | Turns the log level to `DEBUG`.
150 `cloud`          | Removes default values for the `datasource` and `objectstore` configurations.
151 `demo`           | Includes incomplete days and hours into the distribution run, thus creating aggregates for the current day and the current hour (and including both in the respective indices). When running multiple distributions in one hour with this profile, the date aggregate for today and the hours aggregate for the current hour will be updated and overwritten. This profile also turns off the expiry policy (Keys must be expired for at least 2 hours before distribution) and the shifting policy (there must be at least 140 keys in a distribution).
152 `testdata`       | Causes test data to be inserted into the database before each distribution run. By default, around 1000 random diagnosis keys will be generated per hour. If there are no diagnosis keys in the database yet, random keys will be generated for every hour from the beginning of the retention period (14 days ago at 00:00 UTC) until one hour before the present hour. If there are already keys in the database, the random keys will be generated for every hour from the latest diagnosis key in the database (by submission timestamp) until one hour before the present hour (or none at all, if the latest diagnosis key in the database was submitted one hour ago or later).
153 `signature-dev`  | Sets the app package ID in the export packages' signature info to `de.rki.coronawarnapp-dev` so that test certificates (instead of production certificates) will be used for client-side validation.
154 `signature-prod` | Provides production app package IDs for the signature info
155 
156 ### Submission
157 
158 Profile      | Effect
159 -------------|-------------
160 `dev`        | Turns the log level to `DEBUG`.
161 `cloud`      | Removes default values for the `datasource` configuration.
162 
163 ## Documentation
164 
165 The full documentation for the Corona-Warn-App can be found in the [cwa-documentation](https://github.com/corona-warn-app/cwa-documentation) repository. The documentation repository contains technical documents, architecture information, and whitepapers related to this implementation.
166 
167 ## Support and Feedback
168 
169 The following channels are available for discussions, feedback, and support requests:
170 
171 | Type                     | Channel                                                |
172 | ------------------------ | ------------------------------------------------------ |
173 | **General Discussion**   | <a href="https://github.com/corona-warn-app/cwa-documentation/issues/new/choose" title="General Discussion"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-documentation/question.svg?style=flat-square"></a> </a>   |
174 | **Concept Feedback**    | <a href="https://github.com/corona-warn-app/cwa-documentation/issues/new/choose" title="Open Concept Feedback"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-documentation/architecture.svg?style=flat-square"></a>  |
175 | **Backend Issue**    | <a href="https://github.com/corona-warn-app/cwa-server/issues/new/choose" title="Open Backend Issue"><img src="https://img.shields.io/github/issues/corona-warn-app/cwa-server?style=flat-square"></a>  |
176 | **Other Requests**    | <a href="mailto:corona-warn-app.opensource@sap.com" title="Email CWA Team"><img src="https://img.shields.io/badge/email-CWA%20team-green?logo=mail.ru&style=flat-square&logoColor=white"></a>   |
177 
178 ## How to Contribute
179 
180 Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](./CONTRIBUTING.md). By participating in this project, you agree to abide by its [Code of Conduct](./CODE_OF_CONDUCT.md) at all times.
181 
182 ## Contributors
183 
184 The German government has asked SAP and Deutsche Telekom to develop the Corona-Warn-App for Germany as open source software. Deutsche Telekom is providing the network and mobile technology and will operate and run the backend for the app in a safe, scalable and stable manner. SAP is responsible for the app development, its framework and the underlying platform. Therefore, development teams of SAP and Deutsche Telekom are contributing to this project. At the same time our commitment to open source means that we are enabling -in fact encouraging- all interested parties to contribute and become part of its developer community.
185 
186 ## Repositories
187 
188 The following public repositories are currently available for the Corona-Warn-App:
189 
190 | Repository          | Description                                                           |
191 | ------------------- | --------------------------------------------------------------------- |
192 | [cwa-documentation] | Project overview, general documentation, and white papers            |
193 | [cwa-server]        | Backend implementation for the Apple/Google exposure notification API|
194 | [cwa-verification-server] | Backend implementation of the verification process|
195 
196 [cwa-documentation]: https://github.com/corona-warn-app/cwa-documentation
197 [cwa-server]: https://github.com/corona-warn-app/cwa-server
198 [cwa-verification-server]: https://github.com/corona-warn-app/cwa-verification-server
199 [Postgres]: https://www.postgresql.org/
200 [HSQLDB]: http://hsqldb.org/
201 [Zenko CloudServer]: https://github.com/scality/cloudserver
202 
203 ## Licensing
204 
205 Copyright (c) 2020 SAP SE or an SAP affiliate company.
206 
207 Licensed under the **Apache License, Version 2.0** (the "License"); you may not use this file except in compliance with the License.
208 
209 You may obtain a copy of the License at <https://www.apache.org/licenses/LICENSE-2.0>.
210 
211 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the [LICENSE](./LICENSE) for the specific language governing permissions and limitations under the License.
212 
[end of README.md]
[start of /dev/null]
1 
[end of /dev/null]
[start of common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto]
1 syntax = "proto3";
2 package app.coronawarn.server.common.protocols.internal;
3 option java_package = "app.coronawarn.server.common.protocols.internal";
4 option java_multiple_files = true;
5 import "app/coronawarn/server/common/protocols/internal/risk_score_classification.proto";
6 import "app/coronawarn/server/common/protocols/internal/risk_score_parameters.proto";
7 import "app/coronawarn/server/common/protocols/internal/app_version_config.proto";
8 
9 message ApplicationConfiguration {
10 
11   int32 minRiskScore = 1;
12 
13   app.coronawarn.server.common.protocols.internal.RiskScoreClassification riskScoreClasses = 2;
14 
15   app.coronawarn.server.common.protocols.internal.RiskScoreParameters exposureConfig = 3;
16 
17   AttenuationDurationThresholds attenuationDurationThresholds = 4;
18 
19   app.coronawarn.server.common.protocols.internal.ApplicationVersionConfiguration appVersion = 5;
20 }
21 
22 message AttenuationDurationThresholds {
23   int32 lower = 1;
24   int32 upper = 2;
25 }
[end of common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidator.java]
1 /*-
2  * ---license-start
3  * Corona-Warn-App
4  * ---
5  * Copyright (C) 2020 SAP SE and all other contributors
6  * ---
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ---license-end
19  */
20 
21 package app.coronawarn.server.services.distribution.assembly.appconfig.validation;
22 
23 import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.MIN_GREATER_THAN_MAX;
24 import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.VALUE_OUT_OF_BOUNDS;
25 import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MAX;
26 import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MIN;
27 
28 import app.coronawarn.server.common.protocols.internal.ApplicationConfiguration;
29 import app.coronawarn.server.common.protocols.internal.RiskScoreClassification;
30 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
31 
32 /**
33  * This validator validates a {@link ApplicationConfiguration}. It will re-use the {@link ConfigurationValidator} from
34  * the sub-configurations of {@link RiskScoreParameters} and {@link RiskScoreClassification}.
35  */
36 public class ApplicationConfigurationValidator extends ConfigurationValidator {
37 
38   private final ApplicationConfiguration config;
39 
40   /**
41    * Creates a new instance for the given {@link ApplicationConfiguration}.
42    *
43    * @param config the Application Configuration to validate.
44    */
45   public ApplicationConfigurationValidator(ApplicationConfiguration config) {
46     this.config = config;
47   }
48 
49   @Override
50   public ValidationResult validate() {
51     this.errors = new ValidationResult();
52 
53     validateMinRisk();
54     validateAttenuationDurationThresholds();
55 
56     ValidationResult exposureResult = new ExposureConfigurationValidator(config.getExposureConfig()).validate();
57     ValidationResult riskScoreResult = new RiskScoreClassificationValidator(config.getRiskScoreClasses()).validate();
58 
59     return errors.with(exposureResult).with(riskScoreResult);
60   }
61 
62   private void validateMinRisk() {
63     int minLevel = this.config.getMinRiskScore();
64 
65     if (!RiskScoreValidator.isInBounds(minLevel)) {
66       this.errors.add(new MinimumRiskLevelValidationError(minLevel));
67     }
68   }
69 
70   private void validateAttenuationDurationThresholds() {
71     int lower = config.getAttenuationDurationThresholds().getLower();
72     int upper = config.getAttenuationDurationThresholds().getUpper();
73 
74     checkThresholdBound("lower", lower);
75     checkThresholdBound("upper", upper);
76 
77     if (lower > upper) {
78       String parameters = "attenuationDurationThreshold.lower, attenuationDurationThreshold.upper";
79       String values = lower + ", " + upper;
80       this.errors.add(new GeneralValidationError(parameters, values, MIN_GREATER_THAN_MAX));
81     }
82   }
83 
84   private void checkThresholdBound(String boundLabel, int boundValue) {
85     if (boundValue < ATTENUATION_DURATION_THRESHOLD_MIN || boundValue > ATTENUATION_DURATION_THRESHOLD_MAX) {
86       this.errors.add(
87           new GeneralValidationError("attenuationDurationThreshold." + boundLabel, boundValue, VALUE_OUT_OF_BOUNDS));
88     }
89   }
90 }
91 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidator.java]
[start of services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java]
1 /*-
2  * ---license-start
3  * Corona-Warn-App
4  * ---
5  * Copyright (C) 2020 SAP SE and all other contributors
6  * ---
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ---license-end
19  */
20 
21 package app.coronawarn.server.services.distribution.assembly.appconfig.validation;
22 
23 /**
24  * Definition of the spec according to Apple/Google:
25  * https://developer.apple.com/documentation/exposurenotification/enexposureconfiguration
26  */
27 public class ParameterSpec {
28 
29   private ParameterSpec() {
30   }
31 
32   /**
33    * The minimum weight value for mobile API.
34    */
35   public static final double WEIGHT_MIN = 0.001;
36 
37   /**
38    * The maximum weight value for mobile API.
39    */
40   public static final int WEIGHT_MAX = 100;
41 
42   /**
43    * Maximum number of allowed decimals.
44    */
45   public static final int WEIGHT_MAX_DECIMALS = 3;
46 
47   /**
48    * The allowed minimum value for risk score.
49    */
50   public static final int RISK_SCORE_MIN = 0;
51 
52   /**
53    * The allowed maximum value for a risk score.
54    */
55   public static final int RISK_SCORE_MAX = 4096;
56 
57   /**
58    * The allowed minimum value for an attenuation threshold.
59    */
60   public static final int ATTENUATION_DURATION_THRESHOLD_MIN = 0;
61 
62   /**
63    * The allowed maximum value for an attenuation threshold.
64    */
65   public static final int ATTENUATION_DURATION_THRESHOLD_MAX = 100;
66 }
67 
[end of services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java]
[start of services/distribution/src/main/resources/master-config/app-config.yaml]
1 #----------------------------------------------------------------------
2 # This is the Corona Warn App master configuration file.
3 # ----------------------------------------------------------------------
4 #
5 # This configuration file will be fetched by the mobile app in order to function.
6 #
7 # It currently provides settings for minimum risk score, score classes and exposure configuration.
8 #
9 # Change this file with caution!
10 
11 min-risk-score: 90
12 attenuationDurationThresholds:
13   lower: 50
14   upper: 70
15 risk-score-classes: !include risk-score-classification.yaml
16 exposure-config: !include exposure-config.yaml
17 app-version: !include app-version-config.yaml
[end of services/distribution/src/main/resources/master-config/app-config.yaml]
</code>

Here is an example of a patch file. It consists of changes to the code base. It specifies the file names, the line numbers of each change, and the removed and added lines. A single patch file can contain changes to multiple files.
<patch>
--- a/file.py
+++ b/file.py
@@ -1,27 +1,35 @@
 def euclidean(a, b):
-    while b:
-        a, b = b, a % b
-    return a
+    if b == 0:
+        return a
+    return euclidean(b, a % b)
 
 
 def bresenham(x0, y0, x1, y1):
     points = []
     dx = abs(x1 - x0)
     dy = abs(y1 - y0)
-    sx = 1 if x0 < x1 else -1
-    sy = 1 if y0 < y1 else -1
-    err = dx - dy
+    x, y = x0, y0
+    sx = -1 if x0 > x1 else 1
+    sy = -1 if y0 > y1 else 1
 
-    while True:
-        points.append((x0, y0))
-        if x0 == x1 and y0 == y1:
-            break
-        e2 = 2 * err
-        if e2 > -dy:
+    if dx > dy:
+        err = dx / 2.0
+        while x != x1:
+            points.append((x, y))
             err -= dy
-            x0 += sx
-        if e2 < dx:
-            err += dx
-            y0 += sy
+            if err < 0:
+                y += sy
+                err += dx
+            x += sx
+    else:
+        err = dy / 2.0
+        while y != y1:
+            points.append((x, y))
+            err -= dx
+            if err < 0:
+                x += sx
+                err += dy
+            y += sy
 
+    points.append((x, y))
     return points
</patch>

I need you to solve the provded issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the format shown above.
Respond below:


Application Configuration: Attenuation Duration
The RKI requested weighting parameters for the three buckets defined by the attenuation duration threshold. Add this to the attenuation duration configuration.

@michael-burwig 

```yaml
attenuation-duration:
  thresholds:
    lower: 50
    upper: 70
  weights:
    low: 1 # attenuation < 50 dB 
    mid: 0.5 # attenuation >50 dB && < 70 dB 
    high: 0 # attenuation > 70dB is ignored
```

Are the weights in the range of [0, 1]?
Is the threshold value range still [0, 100]?
Thresholds remain unchanged.
For weight, lets go with 0...1 for now. There will be another RKI call in the afternoon which might change that though.
<patch>
diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto
--- a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto
+++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/app_config.proto
@@ -5,6 +5,7 @@ option java_multiple_files = true;
 import "app/coronawarn/server/common/protocols/internal/risk_score_classification.proto";
 import "app/coronawarn/server/common/protocols/internal/risk_score_parameters.proto";
 import "app/coronawarn/server/common/protocols/internal/app_version_config.proto";
+import "app/coronawarn/server/common/protocols/internal/attenuation_duration.proto";
 
 message ApplicationConfiguration {
 
@@ -14,12 +15,7 @@ message ApplicationConfiguration {
 
   app.coronawarn.server.common.protocols.internal.RiskScoreParameters exposureConfig = 3;
 
-  AttenuationDurationThresholds attenuationDurationThresholds = 4;
+  app.coronawarn.server.common.protocols.internal.AttenuationDuration attenuationDuration = 4;
 
   app.coronawarn.server.common.protocols.internal.ApplicationVersionConfiguration appVersion = 5;
 }
-
-message AttenuationDurationThresholds {
-  int32 lower = 1;
-  int32 upper = 2;
-}
\ No newline at end of file
diff --git a/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/attenuation_duration.proto b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/attenuation_duration.proto
new file mode 100644
--- /dev/null
+++ b/common/protocols/src/main/proto/app/coronawarn/server/common/protocols/internal/attenuation_duration.proto
@@ -0,0 +1,20 @@
+syntax = "proto3";
+package app.coronawarn.server.common.protocols.internal;
+option java_package = "app.coronawarn.server.common.protocols.internal";
+option java_multiple_files = true;
+
+message AttenuationDuration {
+  Thresholds thresholds = 1;
+  Weights weights = 2;
+}
+
+message Thresholds {
+  int32 lower = 1;
+  int32 upper = 2;
+}
+
+message Weights {
+  double low = 1;
+  double mid = 2;
+  double high = 3;
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidator.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidator.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidator.java
@@ -20,11 +20,6 @@
 
 package app.coronawarn.server.services.distribution.assembly.appconfig.validation;
 
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.MIN_GREATER_THAN_MAX;
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.VALUE_OUT_OF_BOUNDS;
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MAX;
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MIN;
-
 import app.coronawarn.server.common.protocols.internal.ApplicationConfiguration;
 import app.coronawarn.server.common.protocols.internal.RiskScoreClassification;
 import app.coronawarn.server.common.protocols.internal.RiskScoreParameters;
@@ -51,12 +46,13 @@ public ValidationResult validate() {
     this.errors = new ValidationResult();
 
     validateMinRisk();
-    validateAttenuationDurationThresholds();
 
-    ValidationResult exposureResult = new ExposureConfigurationValidator(config.getExposureConfig()).validate();
-    ValidationResult riskScoreResult = new RiskScoreClassificationValidator(config.getRiskScoreClasses()).validate();
+    errors.with(new ExposureConfigurationValidator(config.getExposureConfig()).validate());
+    errors.with(new RiskScoreClassificationValidator(config.getRiskScoreClasses()).validate());
+    errors.with(new ApplicationVersionConfigurationValidator(config.getAppVersion()).validate());
+    errors.with(new AttenuationDurationValidator(config.getAttenuationDuration()).validate());
 
-    return errors.with(exposureResult).with(riskScoreResult);
+    return errors;
   }
 
   private void validateMinRisk() {
@@ -66,25 +62,4 @@ private void validateMinRisk() {
       this.errors.add(new MinimumRiskLevelValidationError(minLevel));
     }
   }
-
-  private void validateAttenuationDurationThresholds() {
-    int lower = config.getAttenuationDurationThresholds().getLower();
-    int upper = config.getAttenuationDurationThresholds().getUpper();
-
-    checkThresholdBound("lower", lower);
-    checkThresholdBound("upper", upper);
-
-    if (lower > upper) {
-      String parameters = "attenuationDurationThreshold.lower, attenuationDurationThreshold.upper";
-      String values = lower + ", " + upper;
-      this.errors.add(new GeneralValidationError(parameters, values, MIN_GREATER_THAN_MAX));
-    }
-  }
-
-  private void checkThresholdBound(String boundLabel, int boundValue) {
-    if (boundValue < ATTENUATION_DURATION_THRESHOLD_MIN || boundValue > ATTENUATION_DURATION_THRESHOLD_MAX) {
-      this.errors.add(
-          new GeneralValidationError("attenuationDurationThreshold." + boundLabel, boundValue, VALUE_OUT_OF_BOUNDS));
-    }
-  }
 }
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/AttenuationDurationValidator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/AttenuationDurationValidator.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/AttenuationDurationValidator.java
@@ -0,0 +1,94 @@
+/*-
+ * ---license-start
+ * Corona-Warn-App
+ * ---
+ * Copyright (C) 2020 SAP SE and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package app.coronawarn.server.services.distribution.assembly.appconfig.validation;
+
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.MIN_GREATER_THAN_MAX;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.VALUE_OUT_OF_BOUNDS;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MAX;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MIN;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_WEIGHT_MAX;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_WEIGHT_MIN;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.WeightValidationError.ErrorType.OUT_OF_RANGE;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.WeightValidationError.ErrorType.TOO_MANY_DECIMAL_PLACES;
+
+import app.coronawarn.server.common.protocols.internal.AttenuationDuration;
+import java.math.BigDecimal;
+
+/**
+ * The AttenuationDurationValidator validates the values of an associated {@link AttenuationDuration} instance.
+ */
+public class AttenuationDurationValidator extends ConfigurationValidator {
+
+  private final AttenuationDuration attenuationDuration;
+
+  public AttenuationDurationValidator(AttenuationDuration attenuationDuration) {
+    this.attenuationDuration = attenuationDuration;
+  }
+
+  @Override
+  public ValidationResult validate() {
+    errors = new ValidationResult();
+
+    validateThresholds();
+    validateWeights();
+
+    return errors;
+  }
+
+  private void validateThresholds() {
+    int lower = attenuationDuration.getThresholds().getLower();
+    int upper = attenuationDuration.getThresholds().getUpper();
+
+    checkThresholdBound("lower", lower);
+    checkThresholdBound("upper", upper);
+
+    if (lower > upper) {
+      String parameters = "attenuation-duration.thresholds.lower, attenuation-duration.thresholds.upper";
+      String values = lower + ", " + upper;
+      this.errors.add(new GeneralValidationError(parameters, values, MIN_GREATER_THAN_MAX));
+    }
+  }
+
+  private void checkThresholdBound(String thresholdLabel, int thresholdValue) {
+    if (thresholdValue < ATTENUATION_DURATION_THRESHOLD_MIN || thresholdValue > ATTENUATION_DURATION_THRESHOLD_MAX) {
+      this.errors.add(new GeneralValidationError(
+          "attenuation-duration.thresholds." + thresholdLabel, thresholdValue, VALUE_OUT_OF_BOUNDS));
+    }
+  }
+
+  private void validateWeights() {
+    checkWeight("low", attenuationDuration.getWeights().getLow());
+    checkWeight("mid", attenuationDuration.getWeights().getMid());
+    checkWeight("high", attenuationDuration.getWeights().getHigh());
+  }
+
+  private void checkWeight(String weightLabel, double weightValue) {
+    if (weightValue < ATTENUATION_DURATION_WEIGHT_MIN || weightValue > ATTENUATION_DURATION_WEIGHT_MAX) {
+      this.errors.add(new WeightValidationError(
+          "attenuation-duration.weights." + weightLabel, weightValue, OUT_OF_RANGE));
+    }
+
+    if (BigDecimal.valueOf(weightValue).scale() > ParameterSpec.ATTENUATION_DURATION_WEIGHT_MAX_DECIMALS) {
+      this.errors.add(new WeightValidationError(
+          "attenuation-duration.weights." + weightLabel, weightValue, TOO_MANY_DECIMAL_PLACES));
+    }
+  }
+}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ParameterSpec.java
@@ -63,4 +63,19 @@ private ParameterSpec() {
    * The allowed maximum value for an attenuation threshold.
    */
   public static final int ATTENUATION_DURATION_THRESHOLD_MAX = 100;
+
+  /**
+   * The allowed minimum value for an attenuation weight.
+   */
+  public static final double ATTENUATION_DURATION_WEIGHT_MIN = .0;
+
+  /**
+   * The allowed maximum value for an attenuation weight.
+   */
+  public static final double ATTENUATION_DURATION_WEIGHT_MAX = 1.;
+
+  /**
+   * Maximum number of allowed decimals for an attenuation weight.
+   */
+  public static final int ATTENUATION_DURATION_WEIGHT_MAX_DECIMALS = 3;
 }
diff --git a/services/distribution/src/main/resources/master-config/app-config.yaml b/services/distribution/src/main/resources/master-config/app-config.yaml
--- a/services/distribution/src/main/resources/master-config/app-config.yaml
+++ b/services/distribution/src/main/resources/master-config/app-config.yaml
@@ -9,9 +9,7 @@
 # Change this file with caution!
 
 min-risk-score: 90
-attenuationDurationThresholds:
-  lower: 50
-  upper: 70
+attenuation-duration: !include attenuation-duration.yaml
 risk-score-classes: !include risk-score-classification.yaml
 exposure-config: !include exposure-config.yaml
-app-version: !include app-version-config.yaml
\ No newline at end of file
+app-version: !include app-version-config.yaml
diff --git a/services/distribution/src/main/resources/master-config/attenuation-duration.yaml b/services/distribution/src/main/resources/master-config/attenuation-duration.yaml
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/main/resources/master-config/attenuation-duration.yaml
@@ -0,0 +1,15 @@
+# This is the attenuation and duration parameter thresholds. The lower and
+# upper threshold partitions the value range into 3 subsets: low, mid, high
+#
+# Each of the aforementioned partitions has a weight in the range of [0, 1]
+# assigned to it. The number of decimal places is restricted to 3.
+#
+# Change this file with caution!
+
+thresholds:
+  lower: 50
+  upper: 70
+weights:
+  low: 1.0
+  mid: 0.5
+  high: 0.0

</patch>
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/AttenuationDurationMasterFileTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/AttenuationDurationMasterFileTest.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/AttenuationDurationMasterFileTest.java
@@ -0,0 +1,47 @@
+/*-
+ * ---license-start
+ * Corona-Warn-App
+ * ---
+ * Copyright (C) 2020 SAP SE and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package app.coronawarn.server.services.distribution.assembly.appconfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import app.coronawarn.server.common.protocols.internal.AttenuationDuration;
+import app.coronawarn.server.services.distribution.assembly.appconfig.validation.AttenuationDurationValidator;
+import app.coronawarn.server.services.distribution.assembly.appconfig.validation.ConfigurationValidator;
+import app.coronawarn.server.services.distribution.assembly.appconfig.validation.ValidationResult;
+import org.junit.jupiter.api.Test;
+
+/**
+ * This test will verify that the provided attenuation/duration parameters master file is syntactically correct and
+ * according to spec. There should never be any deployment when this test is failing.
+ */
+class AttenuationDurationMasterFileTest {
+
+  private static final ValidationResult SUCCESS = new ValidationResult();
+
+  @Test
+  void testMasterFile() throws UnableToLoadFileException {
+    AttenuationDuration config = ApplicationConfigurationProvider.readMasterFile().getAttenuationDuration();
+
+    ConfigurationValidator validator = new AttenuationDurationValidator(config);
+
+    assertThat(validator.validate()).isEqualTo(SUCCESS);
+  }
+}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidatorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidatorTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidatorTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/ApplicationConfigurationValidatorTest.java
@@ -20,26 +20,16 @@
 
 package app.coronawarn.server.services.distribution.assembly.appconfig.validation;
 
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.MIN_GREATER_THAN_MAX;
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.VALUE_OUT_OF_BOUNDS;
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MAX;
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MIN;
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.RiskScoreClassificationValidatorTest.MINIMAL_RISK_SCORE_CLASSIFICATION;
-import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.RiskScoreClassificationValidatorTest.buildExpectedResult;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-import app.coronawarn.server.common.protocols.internal.ApplicationConfiguration;
-import app.coronawarn.server.common.protocols.internal.AttenuationDurationThresholds;
 import app.coronawarn.server.services.distribution.assembly.appconfig.ApplicationConfigurationProvider;
-import app.coronawarn.server.services.distribution.assembly.appconfig.ExposureConfigurationProvider;
 import app.coronawarn.server.services.distribution.assembly.appconfig.UnableToLoadFileException;
 import java.util.stream.Stream;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.jupiter.params.provider.ValueSource;
 
 class ApplicationConfigurationValidatorTest {
 
@@ -59,43 +49,6 @@ void negative(TestWithExpectedResult test) throws UnableToLoadFileException {
     assertThat(getResultForTest(test)).isEqualTo(test.result);
   }
 
-  @ParameterizedTest
-  @ValueSource(ints = {ATTENUATION_DURATION_THRESHOLD_MIN - 1, ATTENUATION_DURATION_THRESHOLD_MAX + 1})
-  void negativeForAttenuationDurationThresholdOutOfBounds(int invalidThresholdValue) throws Exception {
-    ApplicationConfigurationValidator validator = getValidatorForAttenuationDurationThreshold(
-        invalidThresholdValue, invalidThresholdValue);
-
-    ValidationResult expectedResult = buildExpectedResult(
-        new GeneralValidationError("attenuationDurationThreshold.upper", invalidThresholdValue, VALUE_OUT_OF_BOUNDS),
-        new GeneralValidationError("attenuationDurationThreshold.lower", invalidThresholdValue, VALUE_OUT_OF_BOUNDS));
-
-    assertThat(validator.validate()).isEqualTo(expectedResult);
-  }
-
-  @Test
-  void negativeForUpperAttenuationDurationThresholdLesserThanLower() throws Exception {
-    ApplicationConfigurationValidator validator = getValidatorForAttenuationDurationThreshold(
-        ATTENUATION_DURATION_THRESHOLD_MAX, ATTENUATION_DURATION_THRESHOLD_MIN);
-
-    ValidationResult expectedResult = buildExpectedResult(
-        new GeneralValidationError("attenuationDurationThreshold.lower, attenuationDurationThreshold.upper",
-            (ATTENUATION_DURATION_THRESHOLD_MAX + ", " + ATTENUATION_DURATION_THRESHOLD_MIN), MIN_GREATER_THAN_MAX));
-
-    assertThat(validator.validate()).isEqualTo(expectedResult);
-  }
-
-  private ApplicationConfigurationValidator getValidatorForAttenuationDurationThreshold(int lower, int upper)
-      throws Exception {
-    ApplicationConfiguration appConfig = ApplicationConfiguration.newBuilder()
-        .setMinRiskScore(100)
-        .setRiskScoreClasses(MINIMAL_RISK_SCORE_CLASSIFICATION)
-        .setExposureConfig(ExposureConfigurationProvider.readFile("configtests/exposure-config_ok.yaml"))
-        .setAttenuationDurationThresholds(AttenuationDurationThresholds.newBuilder()
-            .setLower(lower)
-            .setUpper(upper)).build();
-    return new ApplicationConfigurationValidator(appConfig);
-  }
-
   @Test
   void circular() {
     assertThatThrownBy(() -> {
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/AttenuationDurationValidatorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/AttenuationDurationValidatorTest.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/AttenuationDurationValidatorTest.java
@@ -0,0 +1,114 @@
+/*-
+ * ---license-start
+ * Corona-Warn-App
+ * ---
+ * Copyright (C) 2020 SAP SE and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package app.coronawarn.server.services.distribution.assembly.appconfig.validation;
+
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.MIN_GREATER_THAN_MAX;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.GeneralValidationError.ErrorType.VALUE_OUT_OF_BOUNDS;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MAX;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_THRESHOLD_MIN;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_WEIGHT_MAX;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.ParameterSpec.ATTENUATION_DURATION_WEIGHT_MIN;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.RiskScoreClassificationValidatorTest.buildError;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.RiskScoreClassificationValidatorTest.buildExpectedResult;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.WeightValidationError.ErrorType.OUT_OF_RANGE;
+import static app.coronawarn.server.services.distribution.assembly.appconfig.validation.WeightValidationError.ErrorType.TOO_MANY_DECIMAL_PLACES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import app.coronawarn.server.common.protocols.internal.AttenuationDuration;
+import app.coronawarn.server.common.protocols.internal.Thresholds;
+import app.coronawarn.server.common.protocols.internal.Weights;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class AttenuationDurationValidatorTest {
+  private static final Thresholds VALID_THRESHOLDS =
+      buildThresholds(ATTENUATION_DURATION_THRESHOLD_MIN, ATTENUATION_DURATION_THRESHOLD_MAX);
+  private static final Weights VALID_WEIGHTS =
+      buildWeights(ATTENUATION_DURATION_WEIGHT_MAX, ATTENUATION_DURATION_WEIGHT_MAX, ATTENUATION_DURATION_WEIGHT_MAX);
+
+  @ParameterizedTest
+  @ValueSource(ints = {ATTENUATION_DURATION_THRESHOLD_MIN - 1, ATTENUATION_DURATION_THRESHOLD_MAX + 1})
+  void failsIfAttenuationDurationThresholdOutOfBounds(int invalidThresholdValue) {
+    AttenuationDurationValidator validator = buildValidator(
+        buildThresholds(invalidThresholdValue, invalidThresholdValue), VALID_WEIGHTS);
+
+    ValidationResult expectedResult = buildExpectedResult(
+        buildError("attenuation-duration.thresholds.lower", invalidThresholdValue, VALUE_OUT_OF_BOUNDS),
+        buildError("attenuation-duration.thresholds.upper", invalidThresholdValue, VALUE_OUT_OF_BOUNDS));
+
+    assertThat(validator.validate()).isEqualTo(expectedResult);
+  }
+
+  @Test
+  void failsIfUpperAttenuationDurationThresholdLesserThanLower() {
+    AttenuationDurationValidator validator = buildValidator(
+        buildThresholds(ATTENUATION_DURATION_THRESHOLD_MAX, ATTENUATION_DURATION_THRESHOLD_MIN), VALID_WEIGHTS);
+
+    ValidationResult expectedResult = buildExpectedResult(
+        new GeneralValidationError("attenuation-duration.thresholds.lower, attenuation-duration.thresholds.upper",
+            (ATTENUATION_DURATION_THRESHOLD_MAX + ", " + ATTENUATION_DURATION_THRESHOLD_MIN), MIN_GREATER_THAN_MAX));
+
+    assertThat(validator.validate()).isEqualTo(expectedResult);
+  }
+
+  @ParameterizedTest
+  @ValueSource(doubles = {ATTENUATION_DURATION_WEIGHT_MIN - .1, ATTENUATION_DURATION_WEIGHT_MAX + .1})
+  void failsIfWeightsOutOfBounds(double invalidWeightValue) {
+    AttenuationDurationValidator validator = buildValidator(VALID_THRESHOLDS,
+        buildWeights(invalidWeightValue, invalidWeightValue, invalidWeightValue));
+
+    ValidationResult expectedResult = buildExpectedResult(
+        new WeightValidationError("attenuation-duration.weights.low", invalidWeightValue, OUT_OF_RANGE),
+        new WeightValidationError("attenuation-duration.weights.mid", invalidWeightValue, OUT_OF_RANGE),
+        new WeightValidationError("attenuation-duration.weights.high", invalidWeightValue, OUT_OF_RANGE));
+
+    assertThat(validator.validate()).isEqualTo(expectedResult);
+  }
+
+  @Test
+  void failsIfWeightsHaveTooManyDecimalPlaces() {
+    double invalidWeightValue = ATTENUATION_DURATION_WEIGHT_MAX - 0.0000001;
+    AttenuationDurationValidator validator = buildValidator(VALID_THRESHOLDS,
+        buildWeights(invalidWeightValue, invalidWeightValue, invalidWeightValue));
+
+    ValidationResult expectedResult = buildExpectedResult(
+        new WeightValidationError("attenuation-duration.weights.low", invalidWeightValue, TOO_MANY_DECIMAL_PLACES),
+        new WeightValidationError("attenuation-duration.weights.mid", invalidWeightValue, TOO_MANY_DECIMAL_PLACES),
+        new WeightValidationError("attenuation-duration.weights.high", invalidWeightValue, TOO_MANY_DECIMAL_PLACES));
+
+    assertThat(validator.validate()).isEqualTo(expectedResult);
+  }
+
+  private static AttenuationDurationValidator buildValidator(Thresholds thresholds, Weights weights) {
+    return new AttenuationDurationValidator(AttenuationDuration.newBuilder()
+        .setThresholds(thresholds)
+        .setWeights(weights).build());
+  }
+
+  private static Thresholds buildThresholds(int lower, int upper) {
+    return Thresholds.newBuilder().setLower(lower).setUpper(upper).build();
+  }
+
+  private static Weights buildWeights(double low, double mid, double high) {
+    return Weights.newBuilder().setLow(low).setMid(mid).setHigh(high).build();
+  }
+}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidatorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidatorTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidatorTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/appconfig/validation/RiskScoreClassificationValidatorTest.java
@@ -166,7 +166,7 @@ private static RiskScoreClass buildRiskClass(String label, int min, int max, Str
     return RiskScoreClass.newBuilder().setLabel(label).setMin(min).setMax(max).setUrl(url).build();
   }
 
-  public static ValidationResult buildExpectedResult(GeneralValidationError... errors) {
+  public static ValidationResult buildExpectedResult(ValidationError... errors) {
     var validationResult = new ValidationResult();
     Arrays.stream(errors).forEach(validationResult::add);
     return validationResult;
diff --git a/services/distribution/src/test/resources/configtests/app-config_ok.yaml b/services/distribution/src/test/resources/configtests/app-config_ok.yaml
--- a/services/distribution/src/test/resources/configtests/app-config_ok.yaml
+++ b/services/distribution/src/test/resources/configtests/app-config_ok.yaml
@@ -1,7 +1,5 @@
 min-risk-score: 100
-attenuationDurationThresholds:
-  lower: 50
-  upper: 70
+attenuation-duration: !include attenuation-duration.yaml
 risk-score-classes: !include risk-score-class_ok.yaml
 exposure-config: !include exposure-config_ok.yaml
-app-version: !include app-version-config_ok.yaml
\ No newline at end of file
+app-version: !include app-version-config_ok.yaml
diff --git a/services/distribution/src/test/resources/configtests/attenuation-duration.yaml b/services/distribution/src/test/resources/configtests/attenuation-duration.yaml
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/test/resources/configtests/attenuation-duration.yaml
@@ -0,0 +1,7 @@
+thresholds:
+  lower: 50
+  upper: 70
+weights:
+  low: 1.0
+  mid: 0.5
+  high: 0.0
