You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Prepare firmware update."""
__version__ = "0.0.9"
import wget
import zipfile
import tempfile
import shutil
import os
import subprocess
import gzip
import pyminizip
class PrepareFirmware():
"""Firmware prepare class."""
def __init__(self):
"""First init class."""
# Last known firmware version for C300X and C100X models
self.urlC300X = ('https://prodlegrandressourcespkg.blob.core.'
'windows.net/binarycontainer/bt_344642_3_0_0-'
'c300x_010719_1_7_19.bin')
self.urlC100X = ('https://www.homesystems-legrandgroup.com/MatrixENG/'
'liferay/bt_mxLiferayCheckout.jsp?fileFormat=generic&'
'fileName=C100X_010501.fwz&fileId='
'58107.23188.46381.34528')
self.urlMX = ('https://www.homesystems-legrandgroup.com/MatrixENG/'
'liferay/bt_mxLiferayCheckout.jsp?fileFormat=generic&'
'fileName=MX_020216.fwz&fileId='
'58107.23188.56230.57461')
# Variables
self.filename = None
# Contants
self.password = 'C300X'
self.password2 = 'C100X'
self.password3 = 'SMARTDES'
self.password4 = 'MX'
self.workingdir = None
self.prtFrmw = None
self.useWebFirmware = None
self.rootPassword = None
self.SSHcreation = None
self.removeSig = None
self.installMQTT = None
self.notifyNewFirmware = None
self.mntLoc = '/media/mounted'
def main(self):
"""Main function."""
# Get the current working directory
cwd = os.getcwd()
# Ask for model: C300X or C100X
self.model = input('Insert model (C300X, C100X or C300EOS): ')
if self.model == 'C300X':
self.url = self.urlC300X
elif self.model == 'C100X':
self.url = self.urlC100X
elif self.model == 'C300EOS':
self.model = 'MX'
self.url = self.urlMX
#self.model = 'MX'
else:
print('Wrong model ❌')
exit(1)
# Ask for firmware file
self.useWebFirmware = input('Do you want to download the firmware [y] or '
'use an available firmware [n]? (y/n): ')
if self.useWebFirmware == 'y' or self.useWebFirmware == 'Y':
version = self.getVersionFromURL()
self.filename = f'{self.model}_{version}.fwz'
print(f'The program will download the firmware: {self.filename}', flush=True)
elif self.useWebFirmware == 'n' or self.useWebFirmware == 'N':
self.filename = input('Enter the filename in the root directory: ')
print(f'We use the firmware on this folder called: {self.filename}', flush=True)
else:
print('Please use y or n', flush=True)
exit(1)
# Ask for root password
self.rootPassword = input('Enter the BTICINO root '
'password (pwned123): ')
if not self.rootPassword:
self.rootPassword = 'pwned123'
self.SSHcreation = input('Do you want to create an SSH key [y] or '
'use your SSH key [n]? (y/n): ')
if self.SSHcreation == 'y' or self.SSHcreation == 'Y':
print('The program will create SSH key for you.', flush=True)
elif self.SSHcreation == 'n' or self.SSHcreation == 'N':
print('We use SSH on this folder called: bticinokey and '
'bticinokey.pub', flush=True)
else:
print('Please use y or n', flush=True)
exit(1)
# Ask for sig files removal
self.removeSig = input('Do you want to remove Sig files [y] or keep '
'them [n]? (y/n): ')
if self.removeSig == 'y' or self.removeSig == 'Y':
print('The program will remove Sig files.', flush=True)
elif self.removeSig == 'n' or self.removeSig == 'N':
print('The program will keep Sig files.', flush=True)
else:
print('Please use y or n', flush=True)
exit(1)
# Ask for MQTT installation
self.installMQTT = input('Do you want to install MQTT [y] or keep it '
'[n]? (y/n): ')
if self.installMQTT == 'y' or self.installMQTT == 'Y':
print('The program will install MQTT.', flush=True)
elif self.installMQTT == 'n' or self.installMQTT == 'N':
print('The program will keep MQTT.', flush=True)
else:
print('Please use y or n', flush=True)
exit(1)
# Ask for notification when new firmware is available
self.notifyNewFirmware = input('Do you want to be notified when a new '
'firmware is available [y] or not '
'[n]? (y/n): ')
if self.notifyNewFirmware == 'y' or self.notifyNewFirmware == 'Y':
print('App will notify you when a new firmware is '
'available.', flush=True)
elif self.notifyNewFirmware == 'n' or self.notifyNewFirmware == 'N':
print('App will not notify you when a new firmware is '
'available.', flush=True)
else:
print('Please use y or n', flush=True)
exit(1)
self.createTempFolder()
if self.useWebFirmware == 'y' or self.useWebFirmware == 'Y':
self.downloadFirmware()
else:
subprocess.run(['sudo', 'cp', f'{cwd}/{self.filename}', f'{self.workingdir}/{self.filename}'])
filesinsidelist = self.listFilesZIP()
self.selectFirmwareFile(filesinsidelist)
self.unzipFile()
self.unGZfirmware()
if self.removeSig == 'y' or self.removeSig == 'Y':
self.removeSigFiles()
self.umountFirmware()
self.mountFirmware()
rootSeed = self.createRootPassword()
self.setShadowFile(rootSeed)
self.setPasswdFile()
if self.SSHcreation == 'y' or self.SSHcreation == 'Y':
self.createSSHkey()
elif self.SSHcreation == 'n' or self.SSHcreation == 'N':
self.getSSHkey(cwd)
self.setSSHkey()
self.setupSSHkeyRights()
self.enableDropbear()
self.saveVersion(cwd, __version__)
if self.installMQTT == 'y' or self.installMQTT == 'Y':
self.prepareMQTT(cwd)
self.enableMQTT()
if self.notifyNewFirmware == 'n' or self.notifyNewFirmware == 'N':
self.disableNotifyNewFirmware()
self.umountFirmware()
self.GZfirmware()
self.zipFileFirmware(filesinsidelist)
self.moveSSHkeyFileFirmware(cwd)
self.deleteTempFolder()
self.setupFirmwareRights(cwd)
# return to init folder
os.chdir(cwd)
def getVersionFromURL(self, human=False):
"""Get version from URL."""
# url in lowercase
url = self.url.lower()
# search model in url
if self.model == 'C300X':
model = 'c300x'
elif self.model == 'C100X':
model = 'c100x'
elif self.model == 'MX':
model = 'mx'
version = '020216'
return version
# get version
vtxt = url.split(model)[1].split('_')[1]
if not human:
return vtxt[0:6]
else:
# major version
major = (vtxt[0:2]).lstrip('0')
# minor version
minor = (vtxt[2:4]).lstrip('0')
# patch version
patch = (vtxt[4:6]).lstrip('0')
# version
version = major + '.' + minor + '.' + patch
return version
def createTempFolder(self):
"""Create temporary folder."""
print('Creating temporary folder... ', end='', flush=True)
tempdir = tempfile.mkdtemp(prefix="bticino-")
self.workingdir = tempdir
# Change the current working directory
os.chdir(self.workingdir)
print(f'created {self.workingdir} ✅')
return tempdir
def downloadFirmware(self):
"""Main function."""
print('Downloading firmware... ', flush=True)
# Using wget to download the file
wget.download(self.url, f'{self.workingdir}/{self.filename}')
# Using httpx to download the file
# with open(self.filename, 'wb') as f:
# with httpx.stream("GET", url) as r:
# for datachunk in r.iter_bytes():
# f.write(datachunk)
print(f' downloaded {self.filename} ✅')
def listFilesZIP(self):
"""List of files."""
print('Reading files inside firmware... ', end='', flush=True)
# zip file handler
zip = zipfile.ZipFile(f'{self.workingdir}/{self.filename}')
# list available files in the container
filesinsidelist = []
if self.removeSig == 'y' or self.removeSig == 'Y':
for partFirm in zip.namelist():
if 'sig' not in partFirm:
filesinsidelist.append(partFirm)
elif self.removeSig == 'n' or self.removeSig == 'N':
filesinsidelist = zip.namelist()
print('done ✅')
return filesinsidelist
def selectFirmwareFile(self, filesinsidelist):
"""Select firmware file."""
print('Selecting firmware file... ', end='', flush=True)
# Select the firmware file
for partFirm in filesinsidelist:
if 'gz' in partFirm and 'recovery' not in partFirm:
self.prtFrmw = partFirm
print(f'important file is {self.prtFrmw} ✅')
def unzipFile(self):
"""Un zip function."""
print('Unzipping firmware... ', end='', flush=True)
zip_file = f'{self.workingdir}/{self.filename}'
if self.model == 'C300X':
password = self.password
elif self.model == 'C100X':
password = self.password2
elif self.model == 'MX':
password = self.password4
elif self.password4 in zip_file:
password = self.password3
else:
password = False
print('No password found ❌')
return
if password:
print(f'Trying to unzip with password: {password} '
'... (please wait arround 95 seconds) ', end='', flush=True)
try:
with zipfile.ZipFile(zip_file) as zf:
zf.extractall(pwd=bytes(password, 'utf-8'))
except RuntimeError:
print('Wrong password ❌')
exit(1)
# 7z l -slt C300X_010717.fwz check is "Method = ZipCrypto Deflate"
if self.removeSig == 'y' or self.removeSig == 'Y':
subprocess.run(['rm', '-rf', f'{self.workingdir}/*.sig'])
print(f'unzipped {self.filename} ✅')
def removeSigFiles(self):
"""Will remove sig files."""
print('Removing Sig files... ', end='', flush=True)
subprocess.call(f'rm -rf {self.workingdir}/*.sig', shell=True)
print('done ✅')
def unGZfirmware(self):
"""UnGZ firmware."""
print('UnGZ firmware... ', end='', flush=True)
# From btweb_only.ext4.gz to btweb_only.ext4
with gzip.open(f'{self.workingdir}/{self.prtFrmw}', 'rb') as f_in:
with open(f'{self.workingdir}/{self.prtFrmw[:-3]}', 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
print(f'unGZed {self.prtFrmw} ✅')
def mountFirmware(self):
"""Mount firmware."""
print('Mounting firmware... ', end='', flush=True)
# sudo mount -o loop btweb_only.ext4 /media/mounted/
# Make directory mounted
subprocess.run(['sudo', 'mkdir', '-p', self.mntLoc])
subprocess.call(['sudo', 'mount', '-t', 'ext4', '-o', 'loop',
f'{self.workingdir}/{self.prtFrmw[:-3]}',
self.mntLoc])
print(f'mounted on {self.mntLoc} ✅')
def createRootPassword(self):
"""Create root password."""
print('Creating root password... ', end='', flush=True)
# openssl passwd -1 -salt root pwned123
# r = $1$root$0i6hbFPn3JOGMeEF0LgEV1
output = subprocess.run(['openssl', 'passwd', '-1', '-salt', 'root',
self.rootPassword], capture_output=True)
r = str((output.stdout).decode('utf-8'))
# remove last character because it is a newline
result = r[:-1]
# result = r.rstrip()
print(f'created {result} ✅')
return result
def setShadowFile(self, rootSeed):
"""Set shadow file."""
print('Setting shadow file... ', end='', flush=True)
if self.rootPassword:
line1 = f'root2:{rootSeed}:18033:0:99999:7:::\n'
line2 = f'bticino2:{rootSeed}:18033:0:99999:7:::\n'
file_object = open(f'{self.mntLoc}/etc/shadow', 'a')
file_object.write(line1)
file_object.write(line2)
file_object.close()
print('modified ✅')
return
else:
print('failed ❌')
return
def setPasswdFile(self):
"""Set passwd file."""
print('Setting passwd file... ', end='', flush=True)
line1 = 'root2:x:0:0:root:/home/root:/bin/sh\n'
line2 = 'bticino2:x:1000:1000::/home/bticino:/bin/sh\n'
file_object = open(f'{self.mntLoc}/etc/passwd', 'a')
file_object.write(line1)
file_object.write(line2)
file_object.close()
print('modified ✅')
def createSSHkey(self):
"""Create SSH key."""
print('Creating SSH key... ', end='', flush=True)
# ssh-keygen -t rsa -b 4096 -f /tmp/bticinokey -N ""
savedkeyfile = f'{self.workingdir}/bticinokey'
subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '4096',
'-f', savedkeyfile, '-N', ''])
print('created ✅')
def getSSHkey(self, cwd):
"""Get SSH key."""
print('Getting SSH key... ', end='', flush=True)
fles = ['bticinokey.pub', 'bticinokey']
for f in fles:
subprocess.run(['sudo', 'cp', f'{cwd}/{f}',
f'{self.workingdir}/{f}'])
print('files moved ✅')
def setSSHkey(self):
"""Set SSH key."""
print('Setting SSH key... ', end='', flush=True)
# sudo cp /tmp/bticinokey.pub to
# /media/mounted/etc/dropbear/authorized_keys
subprocess.run(['sudo', 'cp', f'{self.workingdir}/bticinokey.pub',
f'{self.mntLoc}/etc/dropbear/authorized_keys'])
# Add public file to .ssh/authorized_keys
subprocess.run(['sudo', 'mkdir', '-p',
f'{self.mntLoc}/home/root/.ssh'])
subprocess.run(['sudo', 'cp', f'{self.workingdir}/bticinokey.pub',
f'{self.mntLoc}/home/root/.ssh/authorized_keys'])
print('set done ✅')
def prepareMQTT(self, cwd):
"""Prepare MQTT."""
print('Preparing MQTT... ', end='', flush=True)
# Create tcpdump2mqtt directory
subprocess.run(['sudo', 'mkdir', '-p',
f'{self.mntLoc}/etc/tcpdump2mqtt'])
subprocess.run(['sudo', 'cp', f'{cwd}/mqtt_scripts/TcpDump2Mqtt',
f'{self.mntLoc}/etc/tcpdump2mqtt/TcpDump2Mqtt'])
subprocess.run(['sudo', 'chmod', '775',
f'{self.mntLoc}/etc/tcpdump2mqtt/TcpDump2Mqtt'])
subprocess.run(['sudo', 'cp', f'{cwd}/mqtt_scripts/TcpDump2Mqtt.conf',
f'{self.mntLoc}/etc/tcpdump2mqtt/TcpDump2Mqtt.conf'])
subprocess.run(['sudo', 'cp', f'{cwd}/mqtt_scripts/TcpDump2Mqtt.sh',
f'{self.mntLoc}/etc/tcpdump2mqtt/TcpDump2Mqtt.sh'])
subprocess.run(['sudo', 'chmod', '775',
f'{self.mntLoc}/etc/tcpdump2mqtt/TcpDump2Mqtt.sh'])
subprocess.run(['sudo', 'cp', f'{cwd}/mqtt_scripts/StartMqttSend',
f'{self.mntLoc}/etc/tcpdump2mqtt/StartMqttSend'])
subprocess.run(['sudo', 'chmod', '775',
f'{self.mntLoc}/etc/tcpdump2mqtt/StartMqttSend'])
subprocess.run(['sudo', 'cp', f'{cwd}/mqtt_scripts/StartMqttReceive',
f'{self.mntLoc}/etc/tcpdump2mqtt/StartMqttReceive'])
subprocess.run(['sudo', 'chmod', '775',
f'{self.mntLoc}/etc/tcpdump2mqtt/StartMqttReceive'])
subprocess.run(['sudo', 'cp', f'{cwd}/mqtt_scripts/filter.py',
f'{self.mntLoc}/home/root/filter.py'])
subprocess.run(['sudo', 'chmod', '775',
f'{self.mntLoc}/home/root/filter.py'])
subprocess.run(['sudo', 'cp', f'{self.mntLoc}/etc/init.d/flexisipsh',
f'{self.mntLoc}/etc/init.d/flexisipsh_bak'])
with open(f'{self.mntLoc}/etc/init.d/flexisipsh', 'r') as f:
contents = f.readlines()
contents.insert(24, '\t/bin/touch /tmp/flexisip_restarted\n')
with open(f'{self.mntLoc}/etc/init.d/flexisipsh', 'w') as f:
contents = ''.join(contents)
f.write(contents)
print('done ✅')
def enableMQTT(self):
"""Enable MQTT."""
print('Enabling MQTT... ', end='', flush=True)
os.chdir(f'{self.mntLoc}/etc/rc5.d')
# create symbolic link
subprocess.call(['sudo', 'ln', '-s', '../tcpdump2mqtt/TcpDump2Mqtt.sh',
'S99TcpDump2Mqtt'])
# return to temporary folder
os.chdir(self.workingdir)
print('done ✅')
def setupSSHkeyRights(self):
"""Setup SSH key rights."""
print('Setting up SSH key rights... ', end='', flush=True)
subprocess.run(['sudo', 'chmod', '600',
f'{self.mntLoc}/etc/dropbear/authorized_keys'])
subprocess.run(['sudo', 'chmod', '600',
f'{self.mntLoc}/home/root/.ssh/authorized_keys'])
print('set to 600 ✅')
def enableDropbear(self):
"""Enable dropbear."""
print('Enabling dropbear... ', end='', flush=True)
# change to mounted folder
os.chdir(f'{self.mntLoc}/etc/rc5.d')
# create symbolic link
subprocess.call(['sudo', 'ln', '-s', '../init.d/dropbear',
'S98dropbear'])
# return to temporary folder
os.chdir(self.workingdir)
print('enabled ✅')
def saveVersion(self, cwd, version):
"""Save version."""
print('Saving version... ', end='', flush=True)
destinationPath = '/home/bticino/sp/patch_github.xml'
# Copy file patch_github.xml to mounted folder
# in /home/bticino/sp/patch_github.xml
fromFile = f'{cwd}/patch_github.xml'
toFile = f'{self.mntLoc}{destinationPath}'
inputFile = open(fromFile, 'r')
lines = inputFile.readlines()
inputFile.close()
outputFile = open(toFile, 'w')
for line in lines:
if '<version>' in line:
line = f' <version>{version}</version>\n'
outputFile.write(line)
outputFile.close()
print(f'saved in {destinationPath} ✅')
def disableNotifyNewFirmware(self):
"""Disable notify new firmware."""
print('Disabling notifications when new '
'firmware... ', end='', flush=True)
# Preparing lines
host1 = 'prodlegrandressourcespkg.blob.core.windows.net'
host2 = 'blob.ams25prdstr02a.store.core.windows.net'
line1 = f'/bin/bt_hosts.sh add {host1} 127.0.0.1'
line2 = f'/bin/bt_hosts.sh add {host2} 127.0.0.1'
# Edit file adding lines after openserver word
with open(f'{self.mntLoc}/etc/init.d/bt_daemon-apps.sh', 'r') as f:
contents = f.readlines()
for i, line in enumerate(contents):
if 'openserver' in line:
contents.insert(i + 1, f'\t{line1}\n\t{line2}\n')
break
with open(f'{self.mntLoc}/etc/init.d/bt_daemon-apps.sh', 'w') as f:
contents = ''.join(contents)
f.write(contents)
print('Editing "/etc/init.d/bt_daemon-apps.sh" done ✅')
def umountFirmware(self):
"""Unmount firmware."""
print('Unmounting firmware... ', end='', flush=True)
subprocess.call(['sudo', 'umount', self.mntLoc])
print('unmounted ✅')
def GZfirmware(self):
"""GZ firmware."""
print('GZ firmware... ', end='', flush=True)
# From btweb_only.ext4 to btweb_only.ext4.gz
with open(f'{self.workingdir}/{self.prtFrmw[:-3]}', 'rb') as f_in:
with gzip.open(f'{self.workingdir}/{self.prtFrmw}', 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
print(f'new GZed {self.prtFrmw} ✅')
def zipFileFirmware(self, filesinsidelist):
"""Adding files in the zip archive."""
print('Adding files in the zip archive... ', end='', flush=True)
a = self.filename
output = a[:-4] + '_new' + a[-4:]
zip_file = f'{self.workingdir}/{output}'
if self.model == 'C300X':
password = self.password
elif self.model == 'C100X':
password = self.password2
elif self.model == 'MX':
password = self.password4
elif self.password3 in zip_file:
password = self.password3
else:
password = False
print('No password found ❌')
return
if password:
pathlist = []
for fil in filesinsidelist:
pathlist.append(f'{self.workingdir}/{fil}')
pyminizip.compress_multiple(pathlist, [], zip_file, password, 5)
print(f'firmware new ziped on {zip_file} ✅')
def moveSSHkeyFileFirmware(self, cwd):
"""Move SSH key file."""
print('Moving SSH key file... ', end='', flush=True)
a = self.filename
output = a[:-4] + '_new' + a[-4:]
fles = ['bticinokey.pub', 'bticinokey', output]
for f in fles:
subprocess.run(['sudo', 'mv',
f'{self.workingdir}/{f}', f'{cwd}/{f}'])
print('files moved ✅')
def deleteTempFolder(self):
"""Delete temporary folder."""
print('Deleting temporary folder... ', end='', flush=True)
shutil.rmtree(self.workingdir)
print(f'deleted {self.workingdir} ✅')
def setupFirmwareRights(self, cwd):
"""Setup firmware rights."""
print('Setting up firmware rights... ', end='', flush=True)
a = self.filename
output = a[:-4] + '_new' + a[-4:]
fles = ['bticinokey.pub', 'bticinokey', output]
for f in fles:
subprocess.run(['sudo', 'chown', '-R', '1000:1000', f'{cwd}/{f}'])
subprocess.run(['sudo', 'chmod', '-R', '755', f'{cwd}/{f}'])
print('rights set ✅')
if __name__ == '__main__':
c = PrepareFirmware()
c.main()
Terminal output
# python3 main.py
Insert model (C300X, C100X or C300EOS): C300EOS
Do you want to download the firmware [y] or use an available firmware [n]? (y/n): y
The program will download the firmware: MX_020216.fwz
Enter the BTICINO root password (pwned123): pwned123
Do you want to create an SSH key [y] or use your SSH key [n]? (y/n): y
The program will create SSH key for you.
Do you want to remove Sig files [y] or keep them [n]? (y/n): y
The program will remove Sig files.
Do you want to install MQTT [y] or keep it [n]? (y/n): y
The program will install MQTT.
Do you want to be notified when a new firmware is available [y] or not [n]? (y/n): y
App will notify you when a new firmware is available.
Creating temporary folder... created /tmp/bticino-r3cleha9 ✅
Downloading firmware...
-1 / unknown downloaded MX_020216.fwz ✅
Reading files inside firmware... done ✅
Selecting firmware file... important file is None ✅
Unzipping firmware... Trying to unzip with password: MX ... (please wait arround 95 seconds) unzipped MX_020216.fwz ✅
UnGZ firmware... Traceback (most recent call last):
File "/usr/src/app/main.py", line 565, in <module>
c.main()
File "/usr/src/app/main.py", line 143, in main
self.unGZfirmware()
File "/usr/src/app/main.py", line 291, in unGZfirmware
with gzip.open(f'{self.workingdir}/{self.prtFrmw}', 'rb') as f_in:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/gzip.py", line 58, in open
binary_file = GzipFile(filename, gz_mode, compresslevel)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/gzip.py", line 174, in __init__
fileobj = self.myfileobj = builtins.open(filename, mode or 'rb')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/bticino-r3cleha9/None'
Maybe someone could assist? I am thinking it might have to do with that the filenames mention SHA256?
The text was updated successfully, but these errors were encountered:
The problem is that the C300EOS firmware archive structure is quite different, have a look there:
We should handle this difference in the script by moving differently across files, but the answer is, before automating the process with the script, have you already tried to hack the firmware successfully? I'm not sure that C300EOS is hackable in the same way as C*00X
Please let us know, otherwise editing the script is just a waste of time.
Hello,
I've downloaded and made changes trying to make firmware for C300 EOS work.
Product page: https://www.legrand.pt/diy/classe300eos/
Product sheet page where the firmware download link was found: https://www.homesystems-legrandgroup.com/home/-/productsheets/2987852
Image of the downloaded firmware extracted
main.py after changes
Terminal output
Maybe someone could assist? I am thinking it might have to do with that the filenames mention SHA256?
The text was updated successfully, but these errors were encountered: