
    NQjj                    :   U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlZddlZddlmZ ddlmZmZmZmZ ddlmZmZmZmZ ddlm Z m!Z!  ej"        e#          Z$dZ%d	e% Z&d
e% dZ'dZ(dZ)ee*e*e*f         Z+i Z,de-d<   dZ.dWdZ/ ee.e/          Z0de-d<   dXdYdZ1dZdZ2ddd[d"Z3d\d#Z4d\d$Z5dd%d]d'Z6d^d+Z7d_d.Z8d`d0Z9dad4Z:dbd7Z;dcd9Z<dd:d;d<dd=dddFZ=	 dedfdHZ>dId<dd:d;d<ddJdgdPZ? G dQ dRe!          Z@dhdUZAdXdidVZBdS )juB  Bitwarden Secrets Manager (`bws` CLI) integration.

Hermes pulls API keys from Bitwarden Secrets Manager at process startup
so they don't have to live in plaintext in ``~/.hermes/.env``.

Design summary
--------------

* The ``bws`` binary is auto-installed into ``<hermes_home>/bin/bws`` on
  first use.  Hermes pins one version (``_BWS_VERSION``) and downloads
  the matching asset from the official GitHub Releases page, verifying
  the SHA-256 against the release's published checksum file.
* The access token is stored in ``~/.hermes/.env`` as
  ``BWS_ACCESS_TOKEN`` (or whatever name the user picked in
  ``secrets.bitwarden.access_token_env``).  This is the one
  bootstrap secret — every other provider key can live in Bitwarden.
* Pulling secrets is a single ``bws secret list <project_id>
  --output json`` call.  We cache the result in-process for
  ``cache_ttl_seconds`` so back-to-back ``hermes`` invocations don't
  hammer the API.
* Failures NEVER block Hermes startup.  Missing binary, no network,
  expired token, etc. all emit a one-line warning and continue with
  whatever credentials ``.env`` already had.

The module is intentionally subprocess-driven rather than going through
the ``bitwarden-sdk-secrets`` Python package: one cross-platform binary
is easier to lazy-install than a wheels-with-Rust-extension dependency.
    )annotationsN)Path)DictListOptionalTuple)CachedFetch	DiskCacheFetchResultis_valid_env_name)	ErrorKindSecretSourcez2.0.0z;https://github.com/bitwarden/sdk-sm/releases/download/bws-vzbws-sha256-checksums-z.txt<      zDict[_CacheKey, _CachedFetch]_CACHEzbws_cache.json	cache_key	_CacheKeyreturnstrc                "    | \  }}}| d| d| S )z:Serialize a cache key to a stable string for JSON storage.| )r   token_fp
project_id
server_urls       B/home/alina/.hermes/hermes-agent/agent/secret_sources/bitwarden.py_cache_key_strr   `   s*    '0$Hj*2222j222    )key_serializerr
   _DISK_CACHE	home_pathOptional[Path]r   c                6    t                               |           S )zReturn the disk cache path under hermes_home/cache/.

    Thin wrapper over the shared DiskCache, kept for tests and any direct
    callers; falls back to `$HERMES_HOME` / `~/.hermes` when home is None.
    )r    pathr!   s    r   _disk_cache_pathr&   k   s     I&&&r   c                 (    ddl m}   |             dz  S )z9Where Hermes stores its managed binaries.  Profile-aware.r   get_hermes_homebin)hermes_constantsr)   r(   s    r   _hermes_bin_dirr,   y   s'    000000?u$$r   Finstall_if_missingr.   boolc                |   t                      t                      z  }|                                r!t          j        |t          j                  r|S t          j        d          }|rt          |          S | rB	 t                      S # t          $ r&}t                              d|           Y d}~dS d}~ww xY wdS )uR  Return a path to a usable ``bws`` binary, or None.

    Resolution order:
      1. ``<hermes_home>/bin/bws``  (our managed copy — preferred)
      2. ``shutil.which("bws")``    (system PATH)

    When ``install_if_missing`` is True and neither resolves, this calls
    :func:`install_bws` to download and verify the pinned version.
    bwszbws auto-install failed: %sN)r,   _platform_binary_nameexistsosaccessX_OKshutilwhichr   install_bws	Exceptionloggerwarning)r.   managedsystemexcs       r   find_bwsr@      s     "7"9"99G~~ BIgrw77 \%  F F|| 	==  	 	 	NN8#>>>44444	 4s   ;B	 	
B9B44B9c                 8    t          j                    dk    rdndS )NWindowszbws.exer1   )platformr>   r   r   r   r2   r2      s     ))Y6699EAr   c                    t          j                    } t          j                                                    }| dk    rdt           dS | dk    r|dv rdnd}d| d	t           dS | d
k    r|dv rdnd}d}	 t          j        ddgdddt
          j                  }d|j        |j	        z                                   v rd}n# t          t
          j        f$ r Y nw xY wd| d| dt           dS t          d|  d|           )u   Map (uname, arch, libc) → the upstream asset filename.

    Asset names follow Rust's target triple convention.  Linux defaults
    to gnu (glibc); we switch to musl only if ldd --version says so.
    Darwinzbws-macos-universal-z.ziprB   )arm64aarch64rG   x86_64zbws-z-pc-windows-msvc-Linuxgnulddz	--versionT   )capture_outputtexttimeoutstdinmuslz-unknown-linux--z+Unsupported platform for bws auto-install:  )rC   r>   machinelower_BWS_VERSION
subprocessrunDEVNULLstdoutstderrOSErrorTimeoutExpiredRuntimeError)r>   rT   archlibcress        r   _platform_asset_namerb      sm    _F  &&((G 9l8888#';;;yy?d??\????#';;;yy	.$# (  C #*sz188::::23 	 	 	D	DdDD4DD,DDDD
HfHHwHH  s   7A
C CC)forcerc   c           	     \   t                      }|                    dd           |t                      z  }|                                r| s|S t	                      }t
           d| }t
           dt           }t          j        d          5 }t          |          }||z  }|t          z  }	t                              d|           t          ||           t          ||	           t          |	|          }
t          |          }|
                                |                                k    rt!          d| d|
 d	|           t#          j        |          5 }t'          |t                                }t)          |||          }d
d
d
           n# 1 swxY w Y   t          j        t-          |          d          \  }}t/          j        |           t3          j        ||           t/          j        |t8          j        t8          j        z  t8          j        z  t8          j         z  t8          j!        z  t8          j"        z  t8          j#        z             t/          j$        ||           d
d
d
           n# 1 swxY w Y   t                              dtJ          |           |S )uZ  Download, verify, and install the pinned ``bws`` binary.

    Returns the path to the installed executable.  Raises on any
    failure (network, checksum, extraction) — callers in the auto-install
    path catch these; the user-facing ``hermes secrets bitwarden setup``
    surface lets them propagate so the wizard can show a clear error.
    T)parentsexist_ok/zhermes-bws-)prefixzDownloading %szChecksum mismatch for z: expected z, got Nz.bws_)dirrh   zInstalled bws %s at %s)&r,   mkdirr2   r3   rb   _BWS_RELEASE_BASE_BWS_CHECKSUM_NAMEtempfileTemporaryDirectoryr   r;   info_http_download_expected_sha256_sha256_filerU   r^   zipfileZipFile_pick_zip_member_safe_extract_membermkstempr   r4   closer7   copy2chmodstatS_IRUSRS_IWUSRS_IXUSRS_IRGRPS_IXGRPS_IROTHS_IXOTHreplacerV   )rc   bin_dirtarget
asset_name	asset_urlchecksum_urltmpdirtmpzip_pathchecksum_pathexpectedactualzfmember	extractedfdstageds                    r   r9   r9      s    GMM$M...,...F}} u %''J$33z33I'>>*<>>L		$M	:	:	: $#f6ll#00$i000y(+++|]333#M:>>h''>>v||~~--5 5 5$5 5,25 5  
 _X&& 	>"%b*?*A*ABBF
 -R==I	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> 	> %#g,,wGGG
F
Y'''
L4<'$,6l!\*l!\*	
 	
 	
 	
66"""I$# $# $# $# $# $# $# $# $# $# $# $# $# $# $#L KK(,???Ms8   CJ .FJ F	J F	 CJ  JJurldestNonec                   t           j                            | ddi          }	 t           j                            |t                    5 }t          |d          5 }t          j        ||           d d d            n# 1 swxY w Y   d d d            d S # 1 swxY w Y   d S # t           j        j	        $ r}t          d|  d|           |d }~ww xY w)Nz
User-Agentzhermes-agent)headers)rO   wbzFailed to download : )urllibrequestRequesturlopen_BWS_DOWNLOAD_TIMEOUTopenr7   copyfileobjerrorURLErrorr^   )r   r   reqrespfr?   s         r   rp   rp     sd   
.
 
 |^.L
 
M
MCH^##C1F#GG 	,4dD!! ,Q"4+++, , , , , , , , , , , , , , ,	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, <  H H H=====>>CGHs_   &B# BA>2B>B	BB	B	B# BB# BB# #C7CCchecksum_filer   c                0   |                      dd          }|                                D ]Q}|                                                                }t	          |          dk    r|d         |k    r
|d         c S Rt          d| d| j                   )	zParse the upstream ``bws-sha256-checksums-X.Y.Z.txt`` file.

    Format is the standard ``sha256sum`` output: ``<hex>  <filename>``,
    one per line.
    utf-8r   )encodingerrorsrL   r   zNo checksum entry for z in )	read_text
splitlinesstripsplitlenr^   name)r   r   rN   linepartss        r   rq   rq     s     ""GI"FFD!!  

""$$u::??uRyJ668OOO
EEE1CEE  r   r$   c                    t          j                    }t          | d          5 t          fdd          D ]}|                    |           	 d d d            n# 1 swxY w Y   |                                S )Nrbc                 .                          d          S )Ni   )read)r   s   r   <lambda>z_sha256_file.<locals>.<lambda>$  s    !&&-- r   r   )hashlibsha256r   iterupdate	hexdigest)r$   hchunkr   s      @r   rr   rr   !  s    A	dD		 Q////55 	 	EHHUOOOO	               ;;==s   ,AA"%A"r   zipfile.ZipFilebinary_namec                    fd|                                  D             }|s0t          d d|                                  dd          d          |                    t                     |d         S )	zFind the binary inside the upstream zip.

    Historically the archive has been flat (``bws`` at the root) but we
    tolerate a top-level directory just in case upstream changes.
    c                R    g | ]#}|                     d           d         k    !|$S )rg   r   )r   ).0nr   s     r   
<listcomp>z$_pick_zip_member.<locals>.<listcomp>/  s2    NNNaggcll2.>+.M.M!.M.M.Mr   zCould not find z% inside downloaded archive (members: N   z...))keyr   )namelistr^   sortr   )r   r   
candidatess    ` r   ru   ru   )  s     ONNNR[[]]NNNJ 
1k 1 1rr*1 1 1
 
 	

 OOOa=r   r   dest_dirc                   t           j                            |          }t           j                            t           j                            ||                    }	 t           j                            ||g          |k    }n# t
          $ r d}Y nw xY w|r||k    rt          d|d          |                     ||           t          |          S )aM  Extract a single archive member, refusing path traversal.

    ``ZipFile.extract`` will happily honour member names containing
    ``../`` or absolute paths, letting a malicious archive write outside
    ``dest_dir`` (a "zip-slip").  We resolve the would-be target and
    confirm it stays within ``dest_dir`` before extracting.
    Fz*Refusing to extract unsafe archive member z%: it escapes the extraction directory)	r4   r$   realpathjoin
commonpath
ValueErrorr^   extractr   )r   r   r   	dest_rootr   	containeds         r   rv   rv   :  s       **IWbgll9f==>>FG&&	6':;;yH		   			 
)++3 3 3 3
 
 	
 JJvy!!!<<s   %B BBtokenc                    t          j        |                     d                                                    dd         S )uE   SHA-256 prefix used as a cache key — never logged, never displayed.r   N   )r   r   encoder   )r   s    r   _token_fingerprintr   Z  s3    >%,,w//00::<<SbSAAr   ,  T )binarycache_ttl_seconds	use_cacher   r!   access_tokenr   r   r   floatr   r    Tuple[Dict[str, str], List[str]]c                V   | st          d          |st          d          t          |           ||pdf}|rkt                              |          }|r|                    |          r	|j        g fS t                              |||          }	|	|	t          |<   |	j        g fS |pt          d          }
|
t          d          t          |
| ||          \  }}t          |t          j                              }|t          |<   |rt                              ||||           ||fS )	uO  Pull the secrets for ``project_id`` from Bitwarden Secrets Manager.

    Returns ``(secrets_dict, warnings_list)``.

    Set ``server_url`` to point at a non-default Bitwarden region or a
    self-hosted instance — e.g. ``https://vault.bitwarden.eu`` for EU
    Cloud accounts.  When empty, ``bws`` uses its built-in default
    (``https://vault.bitwarden.com``, US Cloud).  This is plumbed into
    the subprocess as ``BWS_SERVER_URL``.

    Caching is a two-layer LRU: an in-process dict (for hot-reload paths
    inside one process) and a disk-persisted JSON file under
    ``<hermes_home>/cache/bws_cache.json`` (for back-to-back CLI invocations).
    Both share the same TTL.  Pass ``home_path`` so disk cache lookups find
    the right directory in tests / non-standard installs; otherwise we fall
    back to ``$HERMES_HOME`` / ``~/.hermes``.

    Raises :class:`RuntimeError` for fatal conditions (missing binary,
    auth failure, unparseable output).  Callers in the env_loader path
    catch this and emit a single warning; callers in the user-facing
    setup wizard let it propagate.
    zBitwarden access token is emptyzBitwarden project_id is emptyr   NTr-   u   bws binary not available — auto-install failed and `bws` is not on PATH.  Install manually from https://github.com/bitwarden/sdk-sm/releases or re-run `hermes secrets bitwarden setup`.)secrets
fetched_at)r^   r   r   getis_freshr   r    r   r@   _run_bws_list_CachedFetchtimewrite)r   r   r   r   r   r   r!   r   cacheddisk_cachedr1   r   warningsentrys                 r   fetch_bitwarden_secretsr   _  sb   @  ><=== <:;;;#L11:z?ORPI 
+I&& 	&foo&788 	&>2%%!&&y2CYOO" !,F9&**

5H555C
{0
 
 	
 &c<ZPPGXTY[[AAAEF9 J)U,=yIIIHr   r1   c                   t          |           dd|ddg}t          j                                        }||d<   |                    dd           |r||d<   	 t          j        ||d	d	t          t
          j        
          }nP# t
          j	        $ r}t          dt           d          |d }~wt          $ r}t          d|           |d }~ww xY w|j        dk    rX|j        p|j        pd                                                    dd          }t          d|j         d|d d                    |j                                        }	|	si dgfS 	 t#          j        |	          }
n*# t"          j        $ r}t          d|           |d }~ww xY wt)          |
t*                    s$t          dt-          |
          j                   i }g }|
D ]}t)          |t0                    s|                    d          }|                    d          }t)          |t                     rt)          |t                     smt5          |          s|                    d|d           |||<   ||fS )Nsecretlistz--outputjsonBWS_ACCESS_TOKENNO_COLOR1BWS_SERVER_URLT)envrM   rN   rO   rP   zbws timed out after zs fetching secretszfailed to invoke bws: r   r   zbws exited r      z'bws returned no output (empty project?)zbws returned non-JSON output: zbws returned unexpected shape: r   valuezSkipping secret z: not a valid env-var name)r   r4   environcopy
setdefaultrW   rX   _BWS_RUN_TIMEOUTrY   r]   r^   r\   
returncoder[   rZ   r   r   r   loadsJSONDecodeError
isinstancer   type__name__dictr   _is_valid_env_nameappend)r1   r   r   r   cmdr   procr?   errrawpayloadr   r   itemr   r   s                   r   r   r     s    s88Xvz:v
FC
*//

C*CNN:s###  + *D~$$
 
 
 $   G#3GGG
 
	  D D D9C99::CD ! {/dk/R6688@@LL8$/88S#Y88
 
 	
 +



C ?=>>>L*S// L L LACAABBKL gt$$ 
Fd7mm.DFF
 
 	
 !GH  $%% 	hhuoo!!#s## 	:eS+A+A 	!#&& 	OOD3DDD   Hs<   )B CB))C6C		CE* *F9FFr   )access_token_envr   override_existingr   auto_installr   r!   enabledr  r  r  r   c                   t                      }| s|S t          j                            |d                                          }	|	sd| d|_        |S |s	d|_        |S t          |          }
|
|_        |
	d|_        |S 	 t          |	||
|||          \  }}n-# t          $ r }t          |          |_        |cY d}~S d}~ww xY w||_        |j                            |           |                                D ]\  }}||k    r|j                            |           &|s:t          j                            |          r|j                            |           b|t          j        |<   |j                            |           |S )	u8  Pull secrets from BSM and set them on ``os.environ``.

    This is the function ``load_hermes_dotenv()`` calls after the .env
    files have loaded.  It is intentionally defensive — any failure
    returns a :class:`FetchResult` with ``error`` set; it never raises.

    ``server_url`` selects the Bitwarden region or self-hosted endpoint
    (e.g. ``https://vault.bitwarden.eu`` for EU Cloud).  Empty string
    means use ``bws``'s default (US Cloud).

    Parameters mirror the ``secrets.bitwarden.*`` config keys so the
    caller can just splat the dict in.
    r   &secrets.bitwarden.enabled is true but 3 is not set.  Run `hermes secrets bitwarden setup`.Msecrets.bitwarden.project_id is empty.  Run `hermes secrets bitwarden setup`.r-   Nhbws binary not available and auto-install is disabled.  Run `hermes secrets bitwarden setup` to install.r   r   r   r   r   r!   )r   r4   r   r   r   r   r@   binary_pathr   r^   r   r   r   extenditemsskippedr	  applied)r  r  r   r  r   r  r   r!   resultr   r   r   r   r?   r   r   s                   r   apply_bitwarden_secretsr     s   0 ]]F :>>"2B77==??L >5E > > > 	  4 	 666FF~? 	 3%!/!
 
 
    3xx FN
O8$$$mmoo # #
U""" N!!#&&&  	RZ^^C%8%8 	N!!#&&&
3c""""Ms   B 
C%C :C Cc                  B    e Zd ZdZdZdZdZdZdd
ZddZ	ddZ
ddZdS )BitwardenSourceu   Bitwarden Secrets Manager as a registered secret source.

    Thin adapter over the module's fetch machinery.  ``fetch()`` only
    *fetches* — precedence, override semantics, conflict warnings, and
    the ``os.environ`` writes are the orchestrator's job
    (see ``agent.secret_sources.registry.apply_all``).

    Bitwarden is a **bulk** source: it injects every secret in the
    configured BSM project, so explicit per-var bindings from mapped
    sources (e.g. the 1Password ``env:`` map) outrank it.
    	bitwardenzBitwarden Secrets Managerbulkr1   cfgr  r   r/   c                r    t          t          |t                    o|                    dd                    S )Nr  T)r/   r  r  r   )selfr%  s     r   r  z!BitwardenSource.override_existing[  s0    
 JsD))Pcgg6I4.P.PQQQr   c                    d}t          |t                    r$t          |                    d          p|          }t	          |h          S )Nr   r  )r  r  r   r   	frozenset)r'  r%  	token_envs      r   protected_env_varsz"BitwardenSource.protected_env_varsb  sI    &	c4   	FCGG$677D9EEI)%%%r   c           	     @    ddddddddddd	dd
dddddddddS )NzMaster switchF)descriptiondefaultz0Env var holding the machine-account access tokenr   zBSM project UUIDr   z!Disk+memory cache TTL; 0 disablesr   z&BSM values overwrite .env/shell valuesTz#Auto-download the pinned bws binaryz0Region / self-hosted endpoint (empty = US Cloud))r  r  r   r   r  r  r   r   )r'  s    r   config_schemazBitwardenSource.config_schemah  s    '65IIQ-! ! +=LLB" "
  H" "
  E 
  R '
 
 	
r   r!   r   r   c                d   t          |t                    r|ni }t                      }t          |                    d          pd          }t
          j                            |d                                          }|sd| d|_        t          j
        |_        |S t          |                    d          pd          }|sd|_        t          j
        |_        |S t          |                    dd	                    }t          |
          }||_        |d|_        t          j        |_        |S 	 t!          |                    dd                    }	n# t"          t$          f$ r d}	Y nw xY w	 t'          ||||	t          |                    dd          pd                                          |          \  }
}nN# t(          $ rA}t          |          |_        t+          t          |                    |_        |cY d }~S d }~ww xY w|
|_        |j                            |           |S )Nr  r   r   r  r  r   r  r  Tr-   r  r   r   g     r@r   r  )r  r  r   r   r   r4   r   r   r   r   NOT_CONFIGURED
error_kindr/   r@   r  BINARY_MISSINGr   	TypeErrorr   r   r^   _classify_bws_errorr   r   r  )r'  r%  r!   r  r  r   r   r  r   ttlr   r   r?   s                r   fetchzBitwardenSource.fetch  sY   T**2ccsww'9::P>PQQz~~&6;;AACC 	B9I B B B L !* 8FM..4"55
 	8 L !* 8FMCGGND99::\:::#>C L !* 8FM	 3S99::CC:& 	 	 	CCC		 7)%"%sww|R88>B??EEGG#! ! !GXX  	 	 	s88FL 3CHH = =FMMMMMM	
 !x(((s1   5#E E/.E/3AG 
H6HHHN)r%  r  r   r/   )r%  r  )r   r  )r%  r  r!   r   r   r   )r  
__module____qualname____doc__r   labelshapeschemer  r+  r/  r7  r   r   r   r"  r"  I  s        
 
 D'EEFR R R R& & & &
 
 
 
47 7 7 7 7 7r   r"  messager   c                (   |                                  dv rt          j        S dv sdv rt          j        S t	          fddD                       rt          j        S t	          fddD                       rt          j        S t          j        S )zABest-effort mapping of bws failure text onto the shared taxonomy.z	timed outzbinary not availablezfailed to invokec              3      K   | ]}|v V  	d S Nr   r   toklowereds     r   	<genexpr>z&_classify_bws_error.<locals>.<genexpr>  s?       E Ec3'> E E E E E Er   )unauthorizedzinvalid tokenzaccess token401403c              3      K   | ]}|v V  	d S rA  r   rB  s     r   rE  z&_classify_bws_error.<locals>.<genexpr>  s7       : :c3'> : : : : : :r   )network
connectionresolvedownloaddns)rU   r   TIMEOUTr3  anyAUTH_FAILEDNETWORKINTERNAL)r>  rD  s    @r   r5  r5    s    mmooGg  ((,>',I,I''
 E E E E &D E E E E E %$$
 : : : : &9 : : : : : !  r   c                l    t                                            t                              |            dS )zClear in-process AND disk caches.

    Tests can pass ``home_path`` to scope the disk cleanup to a tmpdir.
    Without it we fall back to the same default resolution as the cache
    writer itself.
    N)r   clearr    r%   s    r   _reset_cache_for_testsrV    s+     LLNNNi     r   )r   r   r   r   rA  )r!   r"   r   r   )r   r   )r.   r/   r   r"   )r   r   )rc   r/   r   r   )r   r   r   r   r   r   )r   r   r   r   r   r   )r$   r   r   r   )r   r   r   r   r   r   )r   r   r   r   r   r   r   r   )r   r   r   r   )r   r   r   r   r   r"   r   r   r   r/   r   r   r!   r"   r   r   )r   )
r1   r   r   r   r   r   r   r   r   r   )r  r/   r  r   r   r   r  r/   r   r   r  r/   r   r   r!   r"   r   r   )r>  r   r   r   )r!   r"   r   r   )Cr:  
__future__r   r   r   loggingr4   rC   r7   r{   rW   rm   r   urllib.errorr   urllib.requestrs   pathlibr   typingr   r   r   r   agent.secret_sources._cacher	   r   r
   r   r   r  agent.secret_sources.baser   r   	getLoggerr  r;   rV   rk   rl   r   r   r   r   r   __annotations___DISK_CACHE_BASENAMEr   r    r&   r,   r@   r2   rb   r9   rp   rq   rr   ru   rv   r   r   r   r   r"  r5  rV  r   r   r   <module>rb     s    : # " " " " "    				                         . . . . . . . . . . . .            > = = = = = = =		8	$	$  Q,PP  @\???     #sC- 	(* * * * * ( 3 3 3 3 #      
' ' ' ' '% % % % ,1      6B B B B( ( ( (V "' : : : : : :zH H H H          "   @B B B B "" $@ @ @ @ @ @H FHE E E E E` /#" $R R R R R Rtp p p p pl p p pf   *! ! ! ! ! ! !r   