internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: Gallopsled/pwntools

Issue:
pwn libcdb file fails if binary doesn't contain b'/bin/sh\x00'
Hello,
the "pwn libcdb file /[something]" is crashing depending whether or not the binary contains the string b'/bin/sh\x00'.

This works:
```
$ pwn libcdb file /bin/bash
[*] bash
    BuildID:     e1da91a3e72343eb054c8c69a8d6b4240acb8b10
    MD5:         c33ad3a4937b1c186a8a1279bb31e702
    SHA1:        d07f822b462ecf5ae31f5ccf1c6657b7505afb3f
    SHA256:      a8334e823ce220c4a375e1d5f32fabc1bd47abb6810760ea4100415b55a097e4
    Symbols:
                         dup2 = 0x302b4
                       printf = not found
                         puts = 0x30024
                         read = 0x30494
                   str_bin_sh = 0x336ca
                       system = not found
                        write = 0x30134

```

This is crashing as search returns :
```
$ pwn libcdb file /bin/ls
[*] ls
    BuildID:     e2ca832f1c2112aea9d7b9bc639e97e873a6b516
    MD5:         df0e7216034340f844de8e3b3c37d32b
    SHA1:        0c5f47f25f4379690945f6e7eaa92e1999d0755d
    SHA256:      9379a0fa9ed1e0b4302c4a2c9b1254d3cd76a9048f0ead3c9e216a5082b536bf
    Symbols:
Traceback (most recent call last):
  File "/usr/bin/pwn", line 33, in <module>
    sys.exit(load_entry_point('pwntools==4.11.1', 'console_scripts', 'pwn')())
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/pwnlib/commandline/main.py", line 58, in main
    commands[args.command](args)
  File "/usr/lib/python3.12/site-packages/pwnlib/commandline/libcdb.py", line 236, in main
    synthetic_symbols = collect_synthetic_symbols(exe)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/pwnlib/commandline/libcdb.py", line 180, in collect_synthetic_symbols
    exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
StopIteration

```




Discussion:

Resolving patch:
diff --git a/pwnlib/commandline/libcdb.py b/pwnlib/commandline/libcdb.py
--- a/pwnlib/commandline/libcdb.py
+++ b/pwnlib/commandline/libcdb.py
@@ -176,9 +176,13 @@ def translate_offset(offs, args, exe):
     return offs
 
 def collect_synthetic_symbols(exe):
-    available_symbols = ['str_bin_sh']
-    exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
-
+    available_symbols = []
+    try:
+        exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
+        available_symbols.append('str_bin_sh')
+    except StopIteration:
+        pass
+        
     libc_start_main_return = exe.libc_start_main_return
     if libc_start_main_return > 0:
         exe.symbols['__libc_start_main_ret'] = libc_start_main_return


Test patch:
Repository: Gallopsled/pwntools

Issue:
Only download debug symbols if ELF does not contain them already
Currently the `libcdb.unstrip_libc` function always looks for debug symbols online and tries to merge them into the libc. This is unnecessary and adding some `ELF.debuginfo` property to know if an ELF contains debug info would help.


Discussion:
Hi @peace-maker,
I would like to contribute to this project, if this issue is still relevant, I would like to work on it.

Thank you
Here are my proposed changes for the same,
1. Before seeking debug info online in the [unstrip_libc](https://github.com/Gallopsled/pwntools/blob/14c0d73f89d285717b3d976d5c2eba5965996ae9/pwnlib/libcdb.py#L295) function, include a check for the existence of debug info using [ELF.debuginfo](https://github.com/Gallopsled/pwntools/blob/14c0d73f89d285717b3d976d5c2eba5965996ae9/pwnlib/elf/elf.py#L2013).

Could you please provide clarification on how we could leverage the `ELF.stripped` functionality in this case?
Also, If you agree with the proposed changes, please assign the issue to me for implementation.

Thank you.
I don't think the .stripped property is useful here. Just checking for debuginfo seems enough.
Thank you @peace-maker !
Can you also clarify if addressing this requires the creation of additional tests?
Would be great, but I don't know how when this is fully transparent inside the unstrip function.

Resolving patch:
diff --git a/pwnlib/libcdb.py b/pwnlib/libcdb.py
--- a/pwnlib/libcdb.py
+++ b/pwnlib/libcdb.py
@@ -291,6 +291,10 @@ def unstrip_libc(filename):
         log.warn_once('Given libc does not have a buildid. Cannot look for debuginfo to unstrip.')
         return False
 
+    if libc.debuginfo:
+        log.debug('Given libc already contains debug information. Skipping unstrip.')
+        return True
+
     log.debug('Trying debuginfod servers: %r', DEBUGINFOD_SERVERS)
 
     for server_url in DEBUGINFOD_SERVERS:


Test patch:
Repository: Gallopsled/pwntools

Issue:
`xor` function raises `TypeError: pop expected at least 1 argument, got 0`
https://github.com/Gallopsled/pwntools/blob/5981c7290dd5190733c1f2e9ccf08633869f8b31/pwnlib/util/fiddling.py#L306-L330

At line 330, it raises the type error `TypeError: pop expected at least 1 argument, got 0` because `pop` method cannot be used without arguments.

I believe that it should be fixed to report proper error message.


Discussion:

Resolving patch:
diff --git a/pwnlib/util/fiddling.py b/pwnlib/util/fiddling.py
--- a/pwnlib/util/fiddling.py
+++ b/pwnlib/util/fiddling.py
@@ -320,14 +320,19 @@ def xor(*args, **kwargs):
        The string of the arguments xor'ed together.
 
     Example:
+
        >>> xor(b'lol', b'hello', 42)
        b'. ***'
+       >>> xor(cut = 'min', other = '')
+       Traceback (most recent call last):
+         ...
+       TypeError: xor() got an unexpected keyword argument 'other'
     """
 
     cut = kwargs.pop('cut', 'max')
 
     if kwargs != {}:
-        raise TypeError("xor() got an unexpected keyword argument '%s'" % kwargs.pop()[0])
+        raise TypeError("xor() got an unexpected keyword argument '%s'" % kwargs.popitem()[0])
 
     if len(args) == 0:
         raise ValueError("Must have something to xor")


Test patch:
Repository: Gallopsled/pwntools

Issue:
Undocumented behaviour of sni kwarg of remote()
In `remote()` 'server_hostname' ssl arg can be set like this:
```python
remote('172.22.0.2', 9090, ssl=True, sni='example.com')
```
This behaviour of `sni` kwarg is undocumented. Currently only behaviour of passing a bool in documented.


Discussion:
Would you be willing to improve the documentation in a pull request please?
I made an issue cause I'm not sure how to document it. xd
I'll ask ppl for some input.

Resolving patch:
diff --git a/pwnlib/tubes/remote.py b/pwnlib/tubes/remote.py
--- a/pwnlib/tubes/remote.py
+++ b/pwnlib/tubes/remote.py
@@ -23,11 +23,11 @@ class remote(sock):
         fam: The string "any", "ipv4" or "ipv6" or an integer to pass to :func:`socket.getaddrinfo`.
         typ: The string "tcp" or "udp" or an integer to pass to :func:`socket.getaddrinfo`.
         timeout: A positive number, None or the string "default".
+        sock(:class:`socket.socket`): Socket to inherit, rather than connecting
         ssl(bool): Wrap the socket with SSL
         ssl_context(ssl.SSLContext): Specify SSLContext used to wrap the socket.
-        sni: Set 'server_hostname' in ssl_args based on the host parameter.
-        sock(socket.socket): Socket to inherit, rather than connecting
-        ssl_args(dict): Pass ssl.wrap_socket named arguments in a dictionary.
+        ssl_args(dict): Pass :func:`ssl.wrap_socket` named arguments in a dictionary.
+        sni(str,bool): Set 'server_hostname' in ssl_args. Set to True to set it based on the host argument. Set to False to not provide any value. Default is True.
 
     Examples:
 
@@ -57,7 +57,7 @@ class remote(sock):
 
     def __init__(self, host, port,
                  fam = "any", typ = "tcp",
-                 ssl=False, sock=None, ssl_context=None, ssl_args=None, sni=True,
+                 sock=None, ssl=False, ssl_context=None, ssl_args=None, sni=True,
                  *args, **kwargs):
         super(remote, self).__init__(*args, **kwargs)
 


Test patch:
Repository: Project-MONAI/MONAI

Issue:
Port spatial flip/mirroring transform
a subtask of #62, port a spatial flipping/mirroring transform into Monai

ref:
https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html


Discussion:

Resolving patch:
diff --git a/monai/transforms/transforms.py b/monai/transforms/transforms.py
--- a/monai/transforms/transforms.py
+++ b/monai/transforms/transforms.py
@@ -62,6 +62,24 @@ def __call__(self, img):
         return rescale_array(img, self.minv, self.maxv, self.dtype)
 
 
+@export
+class Flip:
+    """Reverses the order of elements along the given axis. Preserves shape.
+    Uses np.flip in practice. See numpy.flip for additional details.
+
+    Args:
+        axes (None, int or tuple of ints): Axes along which to flip over. Default is None.
+    """
+
+    def __init__(self, axis=None):
+        assert axis is None or isinstance(axis, (int, list, tuple)), \
+            "axis must be None, int or tuple of ints."
+        self.axis = axis
+
+    def __call__(self, img):
+        return np.flip(img, self.axis)
+
+
 @export
 class ToTensor:
     """


Test patch:
diff --git a/tests/test_flip.py b/tests/test_flip.py
new file mode 100644
--- /dev/null
+++ b/tests/test_flip.py
@@ -0,0 +1,44 @@
+# Copyright 2020 MONAI Consortium
+# 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.
+
+import unittest
+
+import numpy as np
+from parameterized import parameterized
+
+from monai.transforms import Flip
+from tests.utils import NumpyImageTestCase2D
+
+
+class FlipTest(NumpyImageTestCase2D):
+
+    @parameterized.expand([
+        ("wrong_axis", ['s', 1], TypeError),
+        ("not_numbers", 's', AssertionError)
+    ])
+    def test_invalid_inputs(self, _, axis, raises):
+        with self.assertRaises(raises):
+            flip = Flip(axis)
+            flip(self.imt)
+
+    @parameterized.expand([
+        ("no_axis", None),
+        ("one_axis", 1),
+        ("many_axis", [0, 1, 2])
+    ])
+    def test_correct_results(self, _, axis):
+        flip = Flip(axis=axis)
+        expected = np.flip(self.imt, axis)
+        self.assertTrue(np.allclose(expected, flip(self.imt)))
+
+
+if __name__ == '__main__':
+    unittest.main()
Repository: Project-MONAI/MONAI

Issue:
Port additive gaussian noise transform
a subtask of #62, port an additive Gaussian noise transform into Monai (for data augmentation)


Discussion:

Resolving patch:
diff --git a/monai/transforms/transforms.py b/monai/transforms/transforms.py
--- a/monai/transforms/transforms.py
+++ b/monai/transforms/transforms.py
@@ -64,6 +64,24 @@ def __call__(self, img):
         return rescale_array(img, self.minv, self.maxv, self.dtype)
 
 
+@export
+class GaussianNoise(Randomizable):
+    """Add gaussian noise to image.
+
+    Args:
+        mean (float or array of floats): Mean or “centre” of the distribution.
+        scale (float): Standard deviation (spread) of distribution.
+        size (int or tuple of ints): Output shape. Default: None (single value is returned).
+    """
+
+    def __init__(self, mean=0.0, std=0.1):
+        self.mean = mean
+        self.std = std
+
+    def __call__(self, img):
+        return img + self.R.normal(self.mean, self.R.uniform(0, self.std), size=img.shape)
+
+
 @export
 class Flip:
     """Reverses the order of elements along the given axis. Preserves shape.


Test patch:
diff --git a/tests/test_gaussian_noise.py b/tests/test_gaussian_noise.py
new file mode 100644
--- /dev/null
+++ b/tests/test_gaussian_noise.py
@@ -0,0 +1,38 @@
+# Copyright 2020 MONAI Consortium
+# 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.
+
+import unittest
+import numpy as np
+
+from parameterized import parameterized
+
+from monai.transforms import GaussianNoise
+from tests.utils import NumpyImageTestCase2D
+
+
+class GaussianNoiseTest(NumpyImageTestCase2D):
+
+    @parameterized.expand([
+        ("test_zero_mean", 0, 0.1),
+        ("test_non_zero_mean", 1, 0.5)
+    ])
+    def test_correct_results(self, _, mean, std):
+        seed = 42
+        gaussian_fn = GaussianNoise(mean=mean, std=std)
+        gaussian_fn.set_random_state(seed)
+        noised = gaussian_fn(self.imt)
+        np.random.seed(seed)
+        expected = self.imt + np.random.normal(mean, np.random.uniform(0, std), size=self.imt.shape)
+        assert np.allclose(expected, noised)
+
+
+if __name__ == '__main__':
+    unittest.main()
Repository: Project-MONAI/MONAI

Issue:
add delete-keys transform
**Is your feature request related to a problem? Please describe.**
Need a transform to delete unnecessary keys from dict data to release memory.



Discussion:

Resolving patch:
diff --git a/monai/transforms/composables.py b/monai/transforms/composables.py
--- a/monai/transforms/composables.py
+++ b/monai/transforms/composables.py
@@ -883,3 +883,25 @@ def __call__(self, data):
         for key in self.keys:
             d[key] = zoomer(d[key])
         return d
+
+
+@export
+@alias('DeleteKeysD', 'DeleteKeysDict')
+class DeleteKeysd(MapTransform):
+    """
+    Delete specified keys from data dictionary to release memory.
+    It will remove the key-values and copy the others to construct a new dictionary.
+    """
+
+    def __init__(self, keys):
+        """
+        Args:
+            keys (hashable items): keys of the corresponding items to be transformed.
+                See also: monai.transform.composables.MapTransform
+        """
+        MapTransform.__init__(self, keys)
+
+    def __call__(self, data):
+        for key in self.keys:
+            del data[key]
+        return dict(data)


Test patch:
diff --git a/tests/test_delete_keys.py b/tests/test_delete_keys.py
new file mode 100644
--- /dev/null
+++ b/tests/test_delete_keys.py
@@ -0,0 +1,38 @@
+# Copyright 2020 MONAI Consortium
+# 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.
+
+import unittest
+import time
+import sys
+from parameterized import parameterized
+from monai.transforms.composables import DeleteKeysd
+
+TEST_CASE_1 = [
+    {'keys': [str(i) for i in range(30)]},
+    20,
+    648,
+]
+
+
+class TestDeleteKeysd(unittest.TestCase):
+
+    @parameterized.expand([TEST_CASE_1])
+    def test_memory(self, input_param, expected_key_size, expected_mem_size):
+        input_data = dict()
+        for i in range(50):
+            input_data[str(i)] = [time.time()] * 100000
+        result = DeleteKeysd(**input_param)(input_data)
+        self.assertEqual(len(result.keys()), expected_key_size)
+        self.assertEqual(sys.getsizeof(result), expected_mem_size)
+
+
+if __name__ == '__main__':
+    unittest.main()
Repository: Project-MONAI/MONAI

Issue:
update CacheDataset notebook example with multi-threads
**Is your feature request related to a problem? Please describe.**
As we just added multi-threads to CacheDataset, we need to update the examples and notebook.


Discussion:

Resolving patch:
diff --git a/monai/data/dataset.py b/monai/data/dataset.py
--- a/monai/data/dataset.py
+++ b/monai/data/dataset.py
@@ -235,24 +235,28 @@ def __init__(self, data, transform, cache_num=sys.maxsize, cache_rate=1.0, num_w
                 will take the minimum of (cache_num, data_length x cache_rate, data_length).
             cache_rate (float): percentage of cached data in total, default is 1.0 (cache all).
                 will take the minimum of (cache_num, data_length x cache_rate, data_length).
-            num_workers (int): the number of worker processes to use.
+            num_workers (int): the number of worker threads to use.
                 If 0 a single thread will be used. Default is 0.
         """
         if not isinstance(transform, Compose):
             transform = Compose(transform)
         super().__init__(data, transform)
         self.cache_num = min(cache_num, int(len(self) * cache_rate), len(self))
-        self._cache = [None] * self.cache_num
-        print("Load and cache transformed data...")
-        if num_workers > 0:
-            self._item_processed = 0
-            self._thread_lock = threading.Lock()
-            with ThreadPool(num_workers) as p:
-                p.map(self._load_cache_item_thread, [(i, data[i], transform.transforms) for i in range(self.cache_num)])
-        else:
-            for i in range(self.cache_num):
-                self._cache[i] = self._load_cache_item(data[i], transform.transforms)
-                process_bar(i + 1, self.cache_num)
+        if self.cache_num > 0:
+            self._cache = [None] * self.cache_num
+            print('Load and cache transformed data...')
+            if num_workers > 0:
+                self._item_processed = 0
+                self._thread_lock = threading.Lock()
+                with ThreadPool(num_workers) as p:
+                    p.map(
+                        self._load_cache_item_thread, 
+                        [(i, data[i], transform.transforms) for i in range(self.cache_num)]
+                    )
+            else:
+                for i in range(self.cache_num):
+                    self._cache[i] = self._load_cache_item(data[i], transform.transforms)
+                    process_bar(i + 1, self.cache_num)
 
     def _load_cache_item(self, item, transforms):
         for _transform in transforms:


Test patch:
Repository: Project-MONAI/MONAI

Issue:
Bug in RandAffined transformations
**Describe the bug**
When RandAffined is called on a dict containing more than one array (for example an image and a segmentation), and if `self.rand_affine.do_transform` is false, then the transformation returns the wrong result for everything after the first array.


**To Reproduce**
The transforms_demo_2d notebook can be easily repurposed to demonstrate this bug.

**Screenshots**
This is the output from two identical RandAffined transformations.

With prob = 1.0:
![image](https://user-images.githubusercontent.com/11088372/81538294-7b221580-9366-11ea-9556-582629de5e87.png)

With prob = 0.0:
![image](https://user-images.githubusercontent.com/11088372/81538487-b6244900-9366-11ea-938d-7a4eb11db9e2.png)


**Environment (please complete the following information):**
 - OS: Centos 7
 - Python version: 3.7
 - MONAI version [e.g. git commit hash]: 0.1.0+34.gad06dff.dirty

**Additional context**
I did some digging on this in my own fork of the code. The relevant code in RandAffined is:
https://github.com/Project-MONAI/MONAI/blob/ad06dff7f85711048690b2e85c99d51001612708/monai/transforms/spatial/dictionary.py#L313-L330

The bug occurs because 
https://github.com/Project-MONAI/MONAI/blob/ad06dff7f85711048690b2e85c99d51001612708/monai/transforms/spatial/dictionary.py#L321
returns a numpy array, whereas
https://github.com/Project-MONAI/MONAI/blob/ad06dff7f85711048690b2e85c99d51001612708/monai/transforms/spatial/dictionary.py#L319
returns a ``torch.Tensor``. And the ``Resample`` class behaves differently with these different types of input. For ``torch.Tensor`` grids, Resample uses ``clone()``, and operates on a copy, whereas with numpy inputs, ``torch.as_tensor()`` is called which then references the original data. Just changing the constructor from ``torch.as_tensor()`` to ``torch.tensor()`` ensures that torch makes a copy of the grid, which means future calls to ``Resample`` will have the correct grid values.

I'll submit a PR from my fork with the fix?

Note that this bug will also happen with ``Rand2DElasticd`` and ``Rand3DElasticd`` since they follow the same pattern, but this fix to ``Resample`` will also apply to them.




Discussion:
Hi @murraycutforth ,

Thanks very much for your bug report and detailed debug information!
Please submit your fix PR as you said.
Thanks in advance!

Resolving patch:
diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py
--- a/monai/transforms/spatial/array.py
+++ b/monai/transforms/spatial/array.py
@@ -795,7 +795,7 @@ def __call__(self, img, grid, mode="bilinear"):
         """
         if not torch.is_tensor(img):
             img = torch.as_tensor(np.ascontiguousarray(img))
-        grid = torch.as_tensor(np.ascontiguousarray(grid)) if not torch.is_tensor(grid) else grid.detach().clone()
+        grid = torch.tensor(np.ascontiguousarray(grid)) if not torch.is_tensor(grid) else grid.detach().clone()
         if self.device:
             img = img.to(self.device)
             grid = grid.to(self.device)


Test patch:
Repository: Project-MONAI/MONAI

Issue:
Add IdentityTransform with configurable delay
Simulating delays in a transform sequence can be useful for simulating delays that may happen
in real data processing scenarios.  For example, a developer may want to simulate network delays by adding an IdentityTransform with a set delay to simulate slow file loading times.

**Describe the solution you'd like**
Add an IdentityTransform with fixed time delay.

**Additional context**
IdentityTransform can be used to simulating behaviors in testing environments.



Discussion:

Resolving patch:
diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py
--- a/monai/transforms/utility/array.py
+++ b/monai/transforms/utility/array.py
@@ -247,6 +247,6 @@ def __init__(self, delay_time=0.0):
         super().__init__()
         self.delay_time: float = delay_time
 
-    def __call__(self, img):
-        time.sleep(self.delay_time)
+    def __call__(self, img, delay_time=None):
+        time.sleep(self.delay_time if delay_time is None else delay_time)
         return img
diff --git a/monai/transforms/utility/dictionary.py b/monai/transforms/utility/dictionary.py
--- a/monai/transforms/utility/dictionary.py
+++ b/monai/transforms/utility/dictionary.py
@@ -18,6 +18,7 @@
 import numpy as np
 
 from monai.transforms.compose import MapTransform
+from monai.utils.misc import ensure_tuple_rep
 from monai.transforms.utility.array import (
     AddChannel,
     AsChannelFirst,
@@ -255,17 +256,13 @@ def __init__(self, keys, delay_time=0.0):
                 to the keys representing the delay for each key element.
         """
         super().__init__(keys)
-
-        if not isinstance(delay_time, (tuple, list)):
-            delay_time = [delay_time] * len(keys)
-        self.converter_dictionary = dict()
-        for key, keyed_default_delay in zip(self.keys, delay_time):
-            self.converter_dictionary[key] = SimulateDelay(delay_time=keyed_default_delay)
+        self.delay_time = ensure_tuple_rep(delay_time, len(self.keys))
+        self.delayer = SimulateDelay()
 
     def __call__(self, data):
         d = dict(data)
-        for key in self.keys:
-            d[key] = self.converter_dictionary[key](d[key])
+        for idx, key in enumerate(self.keys):
+            d[key] = self.delayer(d[key], delay_time=self.delay_time[idx])
         return d
 
 


Test patch: